Related
I was using this technique (How could I retrieve AWS Lambda public IP address by using Python?) but it gives the IPAddress of the Lambda Server within AWS.
Based on this: How can I retrieve a user's public IP address via Amazon API Gateway + Lambda (node), it looks like I should be able to use
ip_address = event['requestContext']['identity']['sourceIp'];
My handler starts like this:
def lambda_handler(event, context):
but if I do a pprint.pprint(event), I don't see any RequestContext in it, only the "body".
The last comment by FFXSam on the Jonathan answer says "It should be noted that event.requestContext.identity is not present if you're serving a page that's not behind an authorizer.".
I'm not sure what that means or why it is true. I'm using API Gateway, and JavaScript code on the client side is calling it.
I could ask the client coder to send me the local IP Address in the body, but it seems like I should be able to get it in the Lambda function itself.
Someone ask for the events, even though I said it only had the fields being passed in a json element called "body":
code:
print("pprint event:")
pprint.pprint(event)
2021-06-06T13:30:01.231-05:00 pprint event:
2021-06-06T13:30:01.231-05:00 {'body': {'ResponseTimeMilliseconds': 2225,
2021-06-06T13:30:01.231-05:00 'authToken': '12312312',
2021-06-06T13:30:01.231-05:00 'handNumber': 7}}
I rewarded bounty to Muzaffar Shaikh, but here I will give a more thorough explanation, which seems to be lacking on StackOverflow. His answer got the IP Address, but dropped my "body" field, but it certainly pointed me in the right direction.
In the AWS API Gateway tool, click "Resources" then your method (mine was "Post"), then click on "Integration Request" as shown here.
Scroll down to the bottom, and if there are not any templates, type in "application/json" and click the checkbox (note, "application/json" is there in light gray letters, but just clicking the checkbox without typing it doesn't work).
Then I put in the following template:
{
"client_ip" : "$input.params('X-Forwarded-For')",
"user_agent" : "$input.params('User-Agent')",
"body" : $input.json('$.body')
}
NOTE: if I put $input.json('$') or $input.json('body'), I ended up with a "body" field inside my "body" field, which broke the current logic.
When the web page was completed, it looked like this:
The next step is to redeploy your template to the "deploy stage" (environment) you were using.
By the way, when entering the template, if you click "Method Request passthrough" in the "Generate template" drop down box, it will generate a template like this (I didn't use this option, but will read more about it soon):
## See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
## This template will pass through all parameters including path, querystring, header, stage variables, and context through to the integration endpoint via the body/payload
#set($allParams = $input.params())
{
"body-json" : $input.json('$'),
"params" : {
#foreach($type in $allParams.keySet())
#set($params = $allParams.get($type))
"$type" : {
#foreach($paramName in $params.keySet())
"$paramName" : "$util.escapeJavaScript($params.get($paramName))"
#if($foreach.hasNext),#end
#end
}
#if($foreach.hasNext),#end
#end
},
"stage-variables" : {
#foreach($key in $stageVariables.keySet())
"$key" : "$util.escapeJavaScript($stageVariables.get($key))"
#if($foreach.hasNext),#end
#end
},
"context" : {
"account-id" : "$context.identity.accountId",
"api-id" : "$context.apiId",
"api-key" : "$context.identity.apiKey",
"authorizer-principal-id" : "$context.authorizer.principalId",
"caller" : "$context.identity.caller",
"cognito-authentication-provider" : "$context.identity.cognitoAuthenticationProvider",
"cognito-authentication-type" : "$context.identity.cognitoAuthenticationType",
"cognito-identity-id" : "$context.identity.cognitoIdentityId",
"cognito-identity-pool-id" : "$context.identity.cognitoIdentityPoolId",
"http-method" : "$context.httpMethod",
"stage" : "$context.stage",
"source-ip" : "$context.identity.sourceIp",
"user" : "$context.identity.user",
"user-agent" : "$context.identity.userAgent",
"user-arn" : "$context.identity.userArn",
"request-id" : "$context.requestId",
"resource-id" : "$context.resourceId",
"resource-path" : "$context.resourcePath"
}
}
Two reference for mapping templates:
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
https://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html
I still have some research to do as to when to user "$input.params" vs "$context.some_value".
You can try this:
Add the X-Forwarded-For to the "HTTP Request Headers" (goto the API-Gateway configuration -> Resources -> Method Request).
Add a Template with Content-Type: application/json (Resources -> Integration Request -> "Mapping Templates")
Add a Mapping to the template
{
"client_ip" : "$input.params('X-Forwarded-For')",
"user_agent" : "$input.params('User-Agent')"
}
Now the Headers are available in Lambda as expected:
event.client_ip
You can also refer to this link :
https://forums.aws.amazon.com/thread.jspa?messageID=648053
I spent a while pulling my hair out not finding how to add a mapping template in my integration request. Not sure what was happening but for anyone stumbling on this what i did was:
Check the Use Lambda Proxy integration checkbox in Integration Request.
Now i can access my headers very easily in my Python lambda function with event.["headers"].
Adding in screenshots to make it easier to find:
Go to Integration Request in the relevant method of your API. (See how i have already added the X-Forwarded-For header in Method Request, as recommended in other answers)
The checkbox is here:
(I did not have to make a mapping template. Really not sure what is going on here.)
There are a couple of questions that are similar, but none of the answers have so far worked for me.
I have an AWS Lambda function behind an AWS API Gateway powered by Serverless, the Lambda should be returning a PDF document via:
let responseObj = {
statusCode: 200,
isBase64Encoded: true,
headers: {
'Content-type': 'application/pdf',
// 'accept-ranges': 'bytes',
'Content-Disposition': 'attachment; filename=' + pdfName + '.pdf'
},
body: pdfBuffer && pdfBuffer.toString('base64')
}
return responseObj;
when I do an console.log() to AWS CloudWatch of pdfBuffer (before base64, it indeed looks like PDF data:
%PDF-1.4
%����
1 0 obj
<</Creator (Chromium)
/Producer (Skia/PDF m90)
...
Yet when I look in postman, I see in my body:
JVBERi0xLjQKJdPr6eEKMSAwIG9iago8PC9DcmVhdG9yIChDaHJvbWl1bSkKL1Byb2R1Y2VyIChTa2lhL1...
So it's obviously not returning a binary file (my pdf).
Looking at API Gateway, it's been suggested you set Binary Media Types to contain */*.
Now, my API gateway has two end points, when I set it to */*, the PDF serving endpoint does indeed correctly serve my PDF, however I have another endpoint that takes in a body of JSON, and when */* is set under Binary Media Types, it malforms/base64 encodes the JSON input making my CSV endpoint useless.
Setting Binary Media Types to contain application/pdf allows my CSV serving endpoint to work, but my PDF endpoint reverts back to serving up junk data, even when manually setting the Accepts header in postman to application/pdf.
So leaving Binary Media Types as application/pdf, I turn to Resources within the API Gateway UI settings:
Here i'm a little unsure which to edit. It seems i have two options in the sidebar, one for GET and one for OPTIONS:
The OPTIONS - Method Execution allows me to edit the Integration Response whereas the GET - Method Execution does not.
When I edit the Integration Response option, and I set Content Handling to Convert to binary (if needed), there appears to be no change in what is returned to me via Postman.
There must be a step or something I am missing. Setting Binary Media Types to contain */* seems like a broken answer. There must be a way to allow certain endpoints to return binary data (like a pdf file) whilst allowing other endpoints to return or accept non binary data.
I'm going to add an answer as a wiki. I think this is something that will change, certainly screenshots will change over time and should be updated as such rather than new answers added.
To enable Binary output from a lambda you have to disable "Use Lambda Proxy Integration" on the Integration Request page:
By changing this, it will knock out what you'd normally expect in the event object of your Lambda (easily accessible path parameters, body parameters etc), so you'll need to alter the Mapping Templates on the same page of the Integration Request.
For my own needs, I set the Request body passthrough to be When there are no templates defined (recommended) with a Content-Type of: application/pdf and a template of:
## See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
## This template will pass through all parameters including path, querystring, header, stage variables, and context through to the integration endpoint via the body/payload
#set($allParams = $input.params())
{
"body-json" : $input.json('$'),
"params" : {
#foreach($type in $allParams.keySet())
#set($params = $allParams.get($type))
"$type" : {
#foreach($paramName in $params.keySet())
"$paramName" : "$util.escapeJavaScript($params.get($paramName))"
#if($foreach.hasNext),#end
#end
}
#if($foreach.hasNext),#end
#end
},
"stage-variables" : {
#foreach($key in $stageVariables.keySet())
"$key" : "$util.escapeJavaScript($stageVariables.get($key))"
#if($foreach.hasNext),#end
#end
},
"requestContext" : {
"authorizer" : {
#foreach($key in $context.authorizer.keySet())
"$key" : "$util.escapeJavaScript($context.authorizer.get($key))"
#if($foreach.hasNext),#end
#end
},
"account-id" : "$context.identity.accountId",
"api-id" : "$context.apiId",
"api-key" : "$context.identity.apiKey",
"authorizer-principal-id" : "$context.authorizer.principalId",
"caller" : "$context.identity.caller",
"cognito-authentication-provider" : "$context.identity.cognitoAuthenticationProvider",
"cognito-authentication-type" : "$context.identity.cognitoAuthenticationType",
"cognito-identity-id" : "$context.identity.cognitoIdentityId",
"cognito-identity-pool-id" : "$context.identity.cognitoIdentityPoolId",
"http-method" : "$context.httpMethod",
"stage" : "$context.stage",
"source-ip" : "$context.identity.sourceIp",
"user" : "$context.identity.user",
"user-agent" : "$context.identity.userAgent",
"user-arn" : "$context.identity.userArn",
"request-id" : "$context.requestId",
"resource-id" : "$context.resourceId",
"resource-path" : "$context.resourcePath"
}
}
This should now pass through the things you would expect in the event parameter of your Lambda. You might need to change the names within the Mapping Template to match your needs, or within your code in your Lambda.
Your endpoint will still fail to output a PDF correctly though.
We should add a response to the Method Response, for my own purposes, I am adding a 200 response. I add a Response Header of "Content-Type" and a Response Body of "application/pdf" with an Model of "Empty".
At this point, if your Lambda is outputting: return pdfBuffer.toString('base64') you should start seeing the correct headers and base64 body within your curl/postman request:
curl -i --output - --location --request GET 'https://example.com/document-generation/soa/pdf/unitresult/1cc1eece-cf4e-e811-80e5-00155d210d92/2a174f81-a055-e511-80c2-00155d220a0c' \
--header 'Content-Type: application/pdf' \
--header 'Accept: application/pdf' \
--header 'Authorization: abc123
HTTP/2 200
content-type: application/pdf
content-length: 32502
date: Wed, 26 May 2021 09:58:15 GMT
x-amzn-requestid: blah blah
x-amz-apigw-id: anonymous
x-amzn-trace-id: all kinds of things
via: other stuff
x-amz-cf-pop: letters
x-cache: Miss from cloudfront
x-amz-cf-pop: letters
x-amz-cf-id: stuff
"JVBERi0xLjQKJdPr6..."
We now need to look at the Integration Response settings page, which is now enabled due to disabling Use Lambda Proxy Integration.
A 200 Method response status should already be setup, expand that and set the "Content handling" to Convert to binary (if needed). Expand Header Mappings and add a Response header of "Content-Type" with a Mapping value of "'application/pdf'".
If you rerun your curl or Postman request, you should now receive your PDF correctly.
If you need to access the PDF from a website on a different domain, you'll need to allow for CORS. To do this, you'll need to add an OPTIONS method to your PDF serving resource.
In the Integration Request for your new OPTIONS method, you'll need to set it to Mock.
In the Method Response for your OPTIONS method, you'll need to add:
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Access-Control-Allow-Origin
to the Response Headers for 200.
In the Integration Response for your OPTIONS method you'll need to add the following Header Mappings:
Response Header: Access-Control-Allow-Headers
Mapping value: 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'
Response Header: Access-Control-Allow-Methods
Mapping value: 'OPTIONS, GET'
Response Header: Access-Control-Allow-Origin
Mapping value: '*'
If your PDF endpoint is using something other than GET. you will need to change the Mapping Value for Access-Control-Allow-Methods to match the method type e.g. 'OPTIONS, POST' if your pdf is served from a POST method.
You can use serverless-apigw-binary plugin in your serverless.yaml file. The gateway binary plugin helps to generate binary types from api gate way.
Try:
plugins:
- serverless-apigw-binary
custom:
apigwBinary:
types:
- "application/pdf"
Hope that helps.
I am trying to see how to access the request header and body values from with in the lambda code. If the request body is in JSON format, it automatically seems to be parsed and made available in the event object.
How can I access the complete query string, request body, request headers (cookies) for any type of incoming "Content-Type" request inside Lambda ?
The edits below are information I have gathered to help solve the question that may or may not be relevant. Please ignore them if you wish to.
EDIT:
I went through the existing questions on SE here and here.
As per this thread, using $input.json('$') should do the trick. I guess the answers from these links above are already out-dated as API gateway by default seems to recognize JSON in the request and if so makes it available in the event object without any mapping templates being configured.
Setting the mapping as suggested does not work for me. It does not contain the request header information.
Here are screen shots on how it is configured.
The "headers" key returns a blank value. Using $input.params('$') or "$input.params('$')" errors out.
EDIT 2
Tried defining the headers in Method Request. Still not getting the User-Agent value inside lambda.
EDIT 3
I used the following template mapping at the API Gateway
{
"request": $input.json('$'),
"headers": "$input.params()"
}
and the below code in lambda
context.succeed("event.key32:"+JSON.stringify(event, null, 2) );
And the response generated by the API gateway shows this
Looking at the "headers" value in the response, it looks like the AWS-SDK/API gateway/cloudfront strips off all headers received from the HTTP client ? Here is the full text from the JSON returned by the $input.params().header
header={CloudFront-Forwarded-Proto=https, CloudFront-Is-Desktop-Viewer=true, CloudFront-Is-Mobile-Viewer=false, CloudFront-Is-SmartTV-Viewer=false, CloudFront-Is-Tablet-Viewer=false, Content-Type=application/json, Via=1.1 5d53b9570d94ce920abbd471.cloudfront.net (CloudFront), 1.1 95eea7baa7ec95c9a41eca9e3ab7.cloudfront.net (CloudFront), X-Amz-Cf-Id=GBqmObLRy6Iem9bJbVPrrW1K3YoWRDyAaMpv-UkshfCsHAA==, X-Forwarded-For=172.35.96.199, 51.139.183.101, X-Forwarded-Port=443, X-Forwarded-Proto=https}}
It doesn't have the User-Agent string in the header, although as the screenshot shows above, it was sent by the REST client.
Interestingly, the entire query string is made available. Not sure if this is an intended way to access it.
The request headers can be accessed using $input.params('header-name')
Surprisingly, the User-Agent header cannot be accessed with above code. You need to jump through the following hoop to retrieve it:
$context.identity.userAgent
The request body/payload should be accessible using the following code. More reference here, here and here:
{
"reqbody": "$input.path('$')"
}
It is not yet clear if the request body is expected to be in JSON. It needs to be noted that the request is treated as UTF-8 according to this post.
There currently seems to be two bugs:
The "User-Agent" header is missing/being stripped off by the Amazon API.
When the header values contain a double quote ("), the lambda function is not executed. (I do not see a log entry in the cloudwatch logs for such requests). Instead, the http response body contains the following:
{
"Type": "User",
"message": "Could not parse request body into json."
}
An example request that fails in Amazon API
I believe this would need to be corrected to be able to implement the ETag mechanism for caching.
References:
An Etag is expected to be enclosed within double quotes. The browser is expected to send this exact value back through the If-None-Match header, and this is where Amazon API breaks.
Syntax for ETag?
HTTP: max length of etag
http://gsnedders.com/http-entity-tags-confusion
Seems like if no "Content-Type" is sent, AWS API Gateway defaults it to "application/json":
https://forums.aws.amazon.com/thread.jspa?threadID=215471
So just define the Mapping Template for "application/json".
You have to get the information you need in the template mapping and send them back your Lambda function, this is one of the template I used to send information to the Lambda function:
{
"params" : "$input.params()",
"content-type-value" : "$input.params().header.get('Content-Type')",
"body" : "$input.json('$')",
"request-id": "$context.requestId",
"method": "$context.httpMethod",
"resource": "$context.resourcePath",
"id": "$input.params('id')" //This is a path parameter in my case
}
You can do the same, or you can access params.path.id (again in my case). Here is the link to the documentation.
Cheers,
I updated the mapping template I used in the answer to one of the referenced questions to contain the userAgent property.
{
"method": "$context.httpMethod",
"body": $input.json('$'),
"userAgent": "$context.identity.userAgent",
"headers": {
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))" #if($foreach.hasNext),#end
#end
},
"queryParams": {
#foreach($param in $input.params().querystring.keySet())
"$param": "$util.escapeJavaScript($input.params().querystring.get($param))" #if($foreach.hasNext),#end
#end
},
"pathParams": {
#foreach($param in $input.params().path.keySet())
"$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end
#end
}
}
A detailed explanation of the template is available here:
http://kennbrodhagen.net/2015/12/06/how-to-create-a-request-object-for-your-lambda-event-from-api-gateway/
I see in the API Gateway FAQ that it is possible to access the request headers sent to the API Gateway...
If you already utilize OAuth tokens or any other authorization
mechanism, you can easily setup API Gateway not to require signed API
calls and simply forward the token headers to your backend for
verification.
However, I can find no example of how to do so in the documentation and it is unclear how to access this data using Lambda.
I am able to set up an open API and gain access to the JSON object that is part of a POST (Walkthrough: API Gateway and Lambda Functions), but in order to implement a OAuth 2.0 style API with my own provider I need access to the "Authorization" header.
My preference is to set this up using Lambda and Java 8, but an example using node.js would also be helpful in understanding how to accomplish this.
You can use the following Mapping Template in the Integration Request to generically map all path, query, and header parameters into the Lambda event. You will still need to register them in the Method Request section of the API Gateway but you can at least decouple the Mapping Template from the specific parameters you want to use. This way you don't have to change the Mapping Template code each time you change headers, query, or path parameters.
I wrote a blog post that gives more detail and some explanation of the Mapping Template: http://kennbrodhagen.net/2015/12/06/how-to-create-a-request-object-for-your-lambda-event-from-api-gateway/
Here is the Mapping Template you can use:
{
"method": "$context.httpMethod",
"body" : $input.json('$'),
"headers": {
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))" #if($foreach.hasNext),#end
#end
},
"queryParams": {
#foreach($param in $input.params().querystring.keySet())
"$param": "$util.escapeJavaScript($input.params().querystring.get($param))" #if($foreach.hasNext),#end
#end
},
"pathParams": {
#foreach($param in $input.params().path.keySet())
"$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end
#end
}
}
First, you need to trap the Authorization header from the HTTP GET request. Then you need to map that value to the Lambda event object.
Go to the API method dashboard and click on Method Request. In there you can add an HTTP Request Header called Authorization as shown below.
This will trap the Authorization header so you can use it later.
Now go back to the method dashboard and click on Integration Request. From here you can pass the value of the header into the Lambda function by using a mapping like this.
{
"Authorization": "$input.params('Authorization')"
}
Now in your Lambda function you can get the value like this.
event.Authorization
You need to create input mapping inside Integration Request panel on the dashboard screen describing your API method.
Following code translates name query input parameter into Lambda Event input object:
{
"name": "$input.params('name')"
}
Screenshot:
You can find more info about this in the original API Gateway to Lambda input thread on AWS Forums.
while this is an old thread, I have found it best to use lambda proxy integration for the purpose. With this you do not have to configure anything in the API gateway and you get all the headers in your lambda function...
https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
As per Prabhat's answer setting up with the lambda proxy integration request is the simplest way to do this, after which you can access the request headers, path parameters and query parameters via
event['pathParameters']['param1']
event["queryStringParameters"]['queryparam1']
event['requestContext']['identity']['userAgent']
event['requestContext']['identity']['sourceIP']
This is an example event object:
{
"requestContext": {
"elb": {
"targetGroupArn": "arn:aws:elasticloadbalancing:us-east-2:1234567890123:targetgroup/lambda-279xxxxxxx5rsrxxxxxx/49e9d6xxxxxxxxx"
}
},
"httpMethod": "GET",
"path": "/lambda",
"queryStringParameters": {
"query": "1234ABCD"
},
"headers": {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"accept-encoding": "gzip",
"accept-language": "en-US,en;q=0.9",
"connection": "keep-alive",
"host": "lambda-alb-12356789012.us-east-2.elb.amazonaws.com",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
"x-amzn-trace-id": "Root=1-5c5xxxxx-3d683b8xxxxxxxxxx",
"x-forwarded-for": "xx.xx.xxx.xxx",
"x-forwarded-port": "80",
"x-forwarded-proto": "http",
"x-imforwards": "20"
},
"body": "",
"isBase64Encoded": false
}
The event object contains "headers" in it, you can access request headers sent to API gateway by using: event.headers.<header key>
The solution by kennbrodhagen worked great for me, see his answer and blog for the details. Since the poster expressed a preference for Java implementation, and it took me a while to figure out how to implement Kenn's handler in java, I'm just sharing the Java code that corresponds:
public class MyHandler implements RequestHandler<Map<String,Object>,String> {
#Override
public String handleRequest(Map<String,Object> eventMap, Context context) {
LambdaLogger logger = context.getLogger();
logger.log("Body:" + eventMap.get("body"));
logger.log("Headers:" + eventMap.get("headers"));
logger.log("Method:" + eventMap.get("method"));
logger.log("Params:" + eventMap.get("params"));
logger.log("Query:" + eventMap.get("query"));
return("{}");
}
}
For .Net Core 3.1+
If you have enabled Lambda Proxy integration you just have to check the Headers collection in your Lambda handler:
public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest data, ILambdaContext context)
{
if (data.Headers.ContainsKey("x-api-key"))
{
...
}
for instance if we want to use
GET /user?name=bob
or
GET /user/bob
How would you pass both of these examples as a parameter to the Lambda function?
I saw something about setting a "mapped from" in the documentation, but I can't find that setting in the API Gateway console.
method.request.path.parameter-name for a path parameter named parameter-name as defined in the Method Request page.
method.request.querystring.parameter-name for a query string parameter named parameter-name as defined in the Method Request page.
I don't see either of these options even though I defined a query string.
As of September 2017, you no longer have to configure mappings to access the request body.
All you need to do is check, "Use Lambda Proxy integration", under Integration Request, under the resource.
You'll then be able to access query parameters, path parameters and headers like so
event['pathParameters']['param1']
event["queryStringParameters"]['queryparam1']
event['requestContext']['identity']['userAgent']
event['requestContext']['identity']['sourceIP']
The steps to get this working are:
Within the API Gateway Console...
Go to Resources -> Integration Request
Click on the plus or edit icon next to the templates dropdown (odd I know since the template field is already open and the button here looks greyed out)
Explicitly type application/json in the content-type field even though it shows a default (if you don't do this it will not save and will not give you an error message)
put this in the input mapping { "name": "$input.params('name')" }
click on the check box next to the templates dropdown (I'm assuming this is what finally saves it)
I have used this mapping template to provide Body, Headers, Method, Path, and URL Query String Parameters to the Lambda event. I wrote a blog post explaining the template in more detail: http://kennbrodhagen.net/2015/12/06/how-to-create-a-request-object-for-your-lambda-event-from-api-gateway/
Here is the Mapping Template you can use:
{
"method": "$context.httpMethod",
"body" : $input.json('$'),
"headers": {
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))" #if($foreach.hasNext),#end
#end
},
"queryParams": {
#foreach($param in $input.params().querystring.keySet())
"$param": "$util.escapeJavaScript($input.params().querystring.get($param))" #if($foreach.hasNext),#end
#end
},
"pathParams": {
#foreach($param in $input.params().path.keySet())
"$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end
#end
}
}
These days a drop-down template is included in the API Gateway Console on AWS.
For your API, click on the resource name... then GET
Expand "Body Mapping Templates"
Type in
application/json
for Content-Type (must be explicitly typed out) and click the tick
A new window will open with the words "Generate template" and a dropdown (see image).
Select
Method Request passthrough
Then click save
To access any variables, just use the following syntax (this is Python)
e.g. URL:
https://yourURL.execute-api.us-west-2.amazonaws.com/prod/confirmReg?token=12345&uid=5
You can get variables as follows:
from __future__ import print_function
import boto3
import json
print('Loading function')
def lambda_handler(event, context):
print(event['params']['querystring']['token'])
print(event['params']['querystring']['uid'])
So there is no need to explicitly name or map each variable you desire.
In order to pass parameters to your lambda function you need to create a mapping between the API Gateway request and your lambda function. The mapping is done in the Integration Request -> Mapping templates section of the selected API Gateway resource.
Create a mapping of type application/json, then on the right you will edit (click the pencil) the template.
A mapping template is actually a Velocity template where you can use ifs, loops and of course print variables on it. The template has these variables injected where you can access querystring parameters, request headers, etc. individually. With the following code you can re-create the whole querystring:
{
"querystring" : "#foreach($key in $input.params().querystring.keySet())#if($foreach.index > 0)&#end$util.urlEncode($key)=$util.urlEncode($input.params().querystring.get($key))#end",
"body" : $input.json('$')
}
Note: click on the check symbol to save the template. You can test your changes with the "test" button in your resource. But in order to test querystring parameters in the AWS console you will need to define the parameter names in the Method Request section of your resource.
Note: check the Velocity User Guide for more information about the Velocity templating language.
Then in your lambda template you can do the following to get the querystring parsed:
var query = require('querystring').parse(event.querystring)
// access parameters with query['foo'] or query.foo
The accepted answer worked fine for me, but expanding on gimenete's answer, I wanted a generic template I could use to pass through all query/path/header params (just as strings for now), and I came up the following template. I'm posting it here in case someone finds it useful:
#set($keys = [])
#foreach($key in $input.params().querystring.keySet())
#set($success = $keys.add($key))
#end
#foreach($key in $input.params().headers.keySet())
#if(!$keys.contains($key))
#set($success = $keys.add($key))
#end
#end
#foreach($key in $input.params().path.keySet())
#if(!$keys.contains($key))
#set($success = $keys.add($key))
#end
#end
{
#foreach($key in $keys)
"$key": "$util.escapeJavaScript($input.params($key))"#if($foreach.hasNext),#end
#end
}
As part of trying to answer one of my own questions here, I came across this trick.
In the API Gateway mapping template, use the following to give you the complete query string as sent by the HTTP client:
{
"querystring": "$input.params().querystring"
}
The advantage is that you don't have to limit yourself to a set of predefined mapped keys in your query string. Now you can accept any key-value pairs in the query string, if this is how you want to handle.
Note: According to this, only $input.params(x) is listed as a variable made available for the VTL template. It is possible that the internals might change and querystring may no longer be available.
Now you should be able to use the new proxy integration type for Lambda to automatically get the full request in standard shape, rather than configure mappings.
see: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-set-up-lambda-proxy-integration-on-proxy-resource
GET /user?name=bob
{
"name": "$input.params().querystring.get('name')"
}
GET /user/bob
{
"name": "$input.params('name')"
}
The query string is straight forward to parse in javascript in the lambda
for GET /user?name=bob
var name = event.queryStringParameters.name;
This doesn't solve the GET user/bob question though.
A lot of the answers here are great. But I wanted something a little simpler.
I wanted something that will work with the "Hello World" sample for free. This means I wanted a simple produces a request body that matches the query string:
{
#foreach($param in $input.params().querystring.keySet())
"$param": "$util.escapeJavaScript($input.params().querystring.get($param))" #if($foreach.hasNext),#end
#end
}
I think the top answer produces something more useful when building something real, but for getting a quick hello world running using the template from AWS this works great.
The following parameter-mapping example passes all parameters, including path, querystring and header, through to the integration endpoint via a JSON payload
#set($allParams = $input.params())
{
"params" : {
#foreach($type in $allParams.keySet())
#set($params = $allParams.get($type))
"$type" : {
#foreach($paramName in $params.keySet())
"$paramName" : "$util.escapeJavaScript($params.get($paramName))"
#if($foreach.hasNext),#end
#end
}
#if($foreach.hasNext),#end
#end
}
}
In effect, this mapping template outputs all the request parameters in the payload as outlined as follows:
{
"parameters" : {
"path" : {
"path_name" : "path_value",
...
}
"header" : {
"header_name" : "header_value",
...
}
'querystring" : {
"querystring_name" : "querystring_value",
...
}
}
}
Copied from the Amazon API Gateway Developer Guide
For getting query parameters you get them in queryStringParameters object like this
const name = event.queryStringParameters.name;
The second one is a clean URL. If your path is /user/{name}, to get the value you get it from pathParameters object like this
const name = event.pathParameters.name;
Python 3.8 with boto3 v1.16v - 2020 December
For configuring routes, you have to configure API Gateway to accept routes. otherwise other than the base route everything else will end up in a {missing auth token} or something other...
Once you configured API Gateway to accept routes, make sure that you enabled lambda proxy, so that things will work better,
to access routes,
new_route = event['path'] # /{some_url}
to access query parameter
query_param = event['queryStringParameters'][{query_key}]
As #Jonathan's answer, after mark Use Lambda Proxy integration in Integration Request, in your source code you should implement as below format to by pass 502 Bad Gateway error.
NodeJS 8.10:
exports.handler = async (event, context, callback) => {
// TODO: You could get path, parameter, headers, body value from this
const { path, queryStringParameters, headers, body } = event;
const response = {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": JSON.stringify({
path,
query: queryStringParameters,
headers,
body: JSON.parse(body)
}),
"isBase64Encoded": false
};
return response;
};
Don't forget deploy your resource at API Gateway before re-run your API.
Response JSON just return which set in body is correct.
So, you could get path, parameter, headers, body value from event
const { path, queryStringParameters, headers, body } = event;
The Lambda function expects JSON input, therefore parsing the query string is needed. The solution is to change the query string to JSON using the Mapping Template.I used it for C# .NET Core, so the expected input should be a JSON with "queryStringParameters" parameter. Follow these 4 steps below to achieve that:
Open the mapping template of your API Gateway resource and add new application/json content-tyap:
Copy the template below, which parses the query string into JSON, and paste it into the mapping template:
{
"queryStringParameters": {#foreach($key in $input.params().querystring.keySet())#if($foreach.index > 0),#end"$key":"$input.params().querystring.get($key)"#end}
}
In the API Gateway, call your Lambda function and add the following query string (for the example): param1=111¶m2=222¶m3=333
The mapping template should create the JSON output below, which is the input for your Lambda function.
{
"queryStringParameters": {"param3":"333","param1":"111","param2":"222"}
}
You're done. From this point, your Lambda function's logic can use the query string parameters.
Good luck!
exports.handler = async (event) => {
let query = event.queryStringParameters;
console.log(`id: ${query.id}`);
const response = {
statusCode: 200,
body: "Hi",
};
return response;
};
You can used Lambda as "Lambda Proxy Integration" ,ref this [https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html#api-gateway-proxy-integration-lambda-function-python] , options avalible to this lambda are
For Nodejs Lambda
'event.headers', 'event.pathParameters', 'event.body', 'event.stageVariables',
and 'event.requestContext'
For Python Lambda
event['headers']['parametername'] and so on
Refer Doc :
https://docs.aws.amazon.com/apigateway/latest/developerguide/integrating-api-with-aws-services-lambda.html#api-as-lambda-proxy-expose-get-method-with-path-parameters-to-call-lambda-function
You need to modify the Mapping Template
My goal was to pass a query string similar to:
protodb?sql=select * from protodb.prototab
to a Node.js 12 Lambda function via a URL from the API gateway. I tried a number of the ideas from the other answers but really wanted to do something in the most API gateway UI native way possible, so I came up with this that worked for me (as of the UI for API Gateway as of December 2020):
On the API Gateway console for a given API, under resources, select the get method. Then select its Integration Request and fill out the data for the lambda function at the top of the page.
Scroll to the bottom and open up the mapping templates section. Choose Request Body Passthrough when there are no templates defined (recommended).
Click on Add mapping templates and create one with the content-type of application/json and hit the check mark button.
For that mapping template, choose the Method Request passthrough on the drop down list for generate template which will fill the textbox under it with AWS' general way to pass everything.
Hit the save button.
Now when I tested it, I could not get the parameter to come through as event.sql under node JS in the Lambda function. It turns out that when the API gateway sends the URL sql query parameter to the Lambda function, it comes through for Node.js as:
var insql = event.params.querystring.sql;
So the trick that took some time for me was to use JSON.stringify to show the full event stack and then work my way down through the sections to be able to pull out the sql parameter from the query string.
So basically you can use the default passthrough functionality in the API gateway with the trick being how the parameters are passed when you are in the Lambda function.
The way that works for me is to
Go to Integration Request
click URL Query String Parameters
click Add query string
in name field put the query name, which is "name" here
in Mapped From field, put "method.request.querystring.name"
My 2 cents here: Lot of answers suggest to activate the option "Use Lambda Proxy Integration" and get the parameters from $.event.queryStringParameter or $.event.pathParameters. But if you happen to have Access-Control-Allow-Origin (a.k.a. CORS) activated, keep reading.
At the time of this post, Lambda Proxy integration and CORS don't work very well together. My approach was to deactivate the checkbox of Lambda Proxy integration and manually provide a Mapping templates for both request and response as follows:
Request template for application/json:
{
#set($params = $input.params().querystring)
"queryStringParameters" : {
#foreach($param in $params.keySet())
"$param" : "$util.escapeJavaScript($params.get($param))" #if($foreach.hasNext),#end
#end
},
#set($params = $input.params().path)
"pathParameters" : {
#foreach($param in $params.keySet())
"$param" : "$util.escapeJavaScript($params.get($param))" #if($foreach.hasNext),#end
#end
}
}
Mind that I named the properties as queryStringParameters and pathParameters on purpose, to mimic the names that Lambda Proxy integration would have generated. This way my lambdas won't break if one day I activate the Lambda Proxy integration.
Response template for application/json:
#set($payload = $util.parseJson($input.json('$')))
#set($context.responseOverride.status = $payload.statusCode)
$payload.body
How do you read these in your lambda (python)? (assuming parameters are optional)
def handler(event, context):
body = event["queryStringParameters"] or {}
result = myfunction(**body)
return {
"statusCode": code,
"headers": {
"content-type": "application/json",
},
"body": result
}
After reading several of these answers, I used a combination of several in Aug of 2018 to retrieve the query string params through lambda for python 3.6.
First, I went to API Gateway -> My API -> resources (on the left) -> Integration Request. Down at the bottom, select Mapping Templates then for content type enter application/json.
Next, select the Method Request Passthrough template that Amazon provides and select save and deploy your API.
Then in, lambda event['params'] is how you access all of your parameters. For query string: event['params']['querystring']