0

I have created a webhook for Twilio on AWS API Gateway, which is sending the request in application/x-www-form-urlencoded Content-Type. I am unable to write or find a mapping template for Golang. I am following this document - link for creating the API Gateway. I am currently using the below template used in the document -

The API Gateway mapping template I am using:

#set($httpPost = $input.path('$').split("&"))
{
#foreach( $kvPair in $httpPost )
 #set($kvTokenised = $kvPair.split("="))
 #if( $kvTokenised.size() > 1 )
   "$kvTokenised[0]" : "$kvTokenised[1]"#if( $foreach.hasNext ),#end
 #else
   "$kvTokenised[0]" : ""#if( $foreach.hasNext ),#end
 #end
#end
}

The JSON created by API Gateway mapping template:

{
    "ToCountry": "US",
    "ToState": "UT",
    "SmsMessageSid": "SMed65aaxxxxxx5c7938df",
    "NumMedia": "0",
    "ToCity": "",
    "FromZip": "",
    "SmsSid": "SMed65aaxxxxxx938df",
    "FromState": "",
    "SmsStatus": "received",
    "FromCity": "",
    "Body": "Testing+again",
    "FromCountry": "IN",
    "To": "%2B1xxxxxx848",
    "ToZip": "",
    "NumSegments": "1",
    "MessageSid": "SMed65aa5dxxxx7938df",
    "AccountSid": "AC23xxxd98",
    "From": "%2B9xxxxxx90",
    "ApiVersion": "2010-04-01"
}

Lambda Code(Golang)

func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

    fmt.Printf("%+v\n", request)
    fmt.Println("request Body:", request.Body)
    fmt.Println("request HTTPMethod:", request.HTTPMethod)
    fmt.Println("request Headers:", request.Headers)
    fmt.Println("request:", request.RequestContext.RequestID)
}

API Gateway Logs

(442f74ed-39e5-4372-bf85-42bf814f802f) Extended Request Id: EIaYxxMF3lQ=
(442f74ed-39e5-4372-bf85-42bf814f802f) Method request path: {}

(442f74ed-39e5-4372-bf85-42bf814f802f) Method request query string:    {}

(442f74ed-39e5-4372-bf85-42bf814f802f) Method request headers: {Accept=*/*, Cache-Control=max-age=259200, X-Twilio-Signature=ZWg2v7xxxfnBlPyxE=, User-Agent=TwilioProxy/1.1, X-Forwarded-Proto=https, I-Twilio-Idempotency-Token=e5d1xxx221bc4, X-Forwarded-For=54.xxxx.227, Host=xxxxxxx.execute-api.us-east-1.amazonaws.com, X-Forwarded-Port=443, X-Amzn-Trace-Id=Root=1-5de67103-7994dbxxx0dbd872, Content-Type=application/x-www-form-urlencoded}}  

(442f74ed-39e5-4372-bf85-42bf814f802f) Method request body before transformations: ToCountry=US&ToState=UT&SmsMessageSid=SMed65axxx595c7938df&NumMedia=0&ToCity=&FromZip=&SmsSid=SMed65aa5xxccdd595c7938df&FromState=&SmsStatus=received&FromCity=&Body=Good+Day&FromCountry=IN&To=%2Bxxxx848&ToZip=&NumSegments=1&MessageSid=SMed65axxxd595c7938df&AccountSid=AC23a2cbxxx65a66d98&From=%2B9xxxx5590&ApiVersion=2010-04-01

(442f74ed-39e5-4372-bf85-42bf814f802f) Endpoint request URI: https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:6xxxxxxxx6:function:Twillio_connector_test/invocations

(442f74ed-39e5-4372-bf85-42bf814f802f) Endpoint request headers: {x-amzn-lambda-integration-tag=442f74ed-39e5-4372-bf85-42bf814f802f, Authorization=*****27aa7a, X-Amz-Date=20191203T142819Z, x-amzn-apigateway-api-id=xxxxx, X-Amz-Source-Arn=arn:aws:execute-api:us-east-1:69xxxx886:xxxxxxx/v1/POST/message, Accept=application/x-www-form-urlencoded, User-Agent=AmazonAPIGateway_f7504e7yc6, X-Amz-Security-Token=IQoJbxxxhQH [TRUNCATED]

(442f74ed-39e5-4372-bf85-42bf814f802f) Endpoint request body after transformations: 
    {
        "ToCountry": "US",
        "ToState": "UT",
        "SmsMessageSid": "SMed65aaxxxxxx5c7938df",
        "NumMedia": "0",
        "ToCity": "",
        "FromZip": "",
        "SmsSid": "SMed65aaxxxxxx938df",
        "FromState": "",
        "SmsStatus": "received",
        "FromCity": "",
        "Body": "Good+Day",
        "FromCountry": "IN",
        "To": "%2B1xxxxxx848",
        "ToZip": "",
        "NumSegments": "1",
        "MessageSid": "SMed65aa5dxxxx7938df",
        "AccountSid": "AC23xxxd98",
        "From": "%2B9xxxxxx90",
        "ApiVersion": "2010-04-01"
    }

(442f74ed-39e5-4372-bf85-42bf814f802f) Endpoint response headers: {Date=Tue, 03 Dec 2019 14:28:20 GMT, Content-Type=application/json, Content-Length=43, Connection=keep-alive, x-amzn-RequestId=168394b7-c152-4434-af02-03a03b6f3090, x-amzn-Remapped-Content-Length=0, X-Amz-Executed-Version=$LATEST, X-Amzn-Trace-Id=root=1-5de67103-7994dbxxxxbe30dbd872;sampled=0}

(442f74ed-39e5-4372-bf85-42bf814f802f) Endpoint response body before transformations: "Lambda function is completed successfully"

(442f74ed-39e5-4372-bf85-42bf814f802f) Method response body after transformations: Lambda function is completed successfully

(442f74ed-39e5-4372-bf85-42bf814f802f) Method response headers: {X-Amzn-Trace-Id=Root=1-5de67103-7994dbxxxxxxd872;Sampled=0, Content-Type=application/xml}

Lambda function logs

request: {   map[] map[] map[] map[] map[] map[] {    {           }  map[]  } Good Day false}
{Resource: Path: HTTPMethod: Headers:map[] MultiValueHeaders:map[] QueryStringParameters:map[] MultiValueQueryStringParameters:map[] PathParameters:map[] StageVariables:map[] RequestContext:{AccountID: ResourceID: Stage: RequestID: Identity:{CognitoIdentityPoolID: AccountID: CognitoIdentityID: Caller: APIKey: AccessKey: SourceIP: CognitoAuthenticationType: CognitoAuthenticationProvider: UserArn: UserAgent: User:} ResourcePath: Authorizer:map[] HTTPMethod: APIID:} Body:Good Day IsBase64Encoded:false}
request Body: Good Day
request HTTPMethod: 
request Headers: map[]

In the Lambda logs, I can see that only body of created JSON gets mapped to events.APIGatewayProxyRequest and not other parameters. And the reason is The JSON created by mapping template is not in the below format -

The events.APIGatewayProxyRequest parameter in golang:
// APIGatewayProxyRequest contains data coming from the API Gateway proxy

type APIGatewayProxyRequest struct {
    Resource                        string                        `json:"resource"` // The resource path defined in API Gateway
    Path                            string                        `json:"path"`     // The url path for the caller
    HTTPMethod                      string                        `json:"httpMethod"`
    Headers                         map[string]string             `json:"headers"`
    MultiValueHeaders               map[string][]string           `json:"multiValueHeaders"`
    QueryStringParameters           map[string]string             `json:"queryStringParameters"`
    MultiValueQueryStringParameters map[string][]string           `json:"multiValueQueryStringParameters"`
    PathParameters                  map[string]string             `json:"pathParameters"`
    StageVariables                  map[string]string             `json:"stageVariables"`
    RequestContext                  APIGatewayProxyRequestContext `json:"requestContext"`
    Body                            string                        `json:"body"`
    IsBase64Encoded                 bool                          `json:"isBase64Encoded,omitempty"`
}

I would appreciate it if anyone can point me to the related document. Suggestions are welcome.

Rahul Satal
  • 2,107
  • 3
  • 32
  • 53

1 Answers1

1

It looks like you might be trying to use the Proxy Integration handler signature to work with a Lambda Custom Integration event on a specific resource. I don't believe mapping templates are relevant in the case of a Proxy Integration.

Lambda Integration

There are two main ways to use Lambda to respond to API Gateway requests. The first is to use a Lambda integration. This method usually involves transforming the request into a custom Lambda event completely up to your discretion. Most of the heavy lifting is done by API Gateway. For instance you could transform a query parameter into just a string and then feed just that string to your Lambda handler.

func handler(e string) (<something>, error) {}

Or you could take a value from a header and a part from the URL and then build a JSON object to feed to your handler.

type event struct {
    URLPart string `json:"url_part"`
    QueryPart string `json:"query_part"`
}

func handler(e *event) (<something>, error) {} 

Main Points

  • You control the request event that gets to your function completely
  • You control the response returned by your Lambda completely
  • More heavy lifting is done by the API Gateway

Proxy Integration

Then there is a Proxy Integration. A Proxy Integration transforms every request sent to the resource (usually the ANY method on a {proxy+} resource) you specify in API gateway as the events.APIGatewayProxyRequest struct type defined in the events module. This takes away the need for you to transform your requests into specific events you have to specify and removes the request heavy lifting from API Gateway and drops it into your Lambda handler.

The proxy integration handler must have the following signature:

func handler(c context.Context, e events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {}

Main Points

  • The request and response is always the same, events.APIGatewayProxyRequest and events.APIGatewayProxyResponse
  • Much less work needs to be done in API Gateway
  • Usually used when you already have a lot invested in a Golang solution and don't want to break it up into individual resource pieces for the Lambda Integration.

Here is a link that explains more of the differences and some considerations for each.

Melignus
  • 149
  • 6
  • Thanks for the explanation. Actually, I was trying to implement `Lambda Integrations` to convert the incoming `XML` data to `JSON` data through the API Gateway. I think we would need a mapping template for this? Anyways, I have used `Lambda Proxy Integrations` instead to pass the `XML` as it is to Lambda and process it there. – Rahul Satal Dec 16 '19 at 10:28
  • If you define incoming data as an XML tagged struct in Go, you can avoid the conversion in API Gateway altogether and unmarshal inside your app. I think one of the problems is that there isn't a prescribed event signature when using Lambda Integration, it's up to you do handle that which is why tagging and unmarshaling might be easier. – Melignus Jan 02 '20 at 21:53