Using AWS API Gateway to build a patch method - amazon-web-services

I am using API Gateway to build a patch method.
In then Integration Request - Mapping Template i added:
{ "id": "$input.params('subscription-id')",
"env": "$stageVariables['env']",
"street": $input.json('street'),
"address_name": $input.json('address_name'),
"payment_day": $input.json('payment_day'),
}
As a patch http method, the user's API is not required to pass all the parameters.
So if the user doesn't pass, for e.g. payment_day, the field is going to be ''. The '' can be a valid value field. So i have two options:
Put a NULL value on the payment_day field.
Remove the payment_day from JSON request.
Is it possible to do this on API Gateway Integration Request -Mapping Template? Does anyone has a workaround?

You can use Velocity Conditionals to only output optional values if present.
Alternately, as mentioned in comments, you can just pass the entire JSON body using $input.json('$') and handle the presence or lack there of inside your Lambda function.

Related

Can AWS API Gateway support `application/x-www-form-urlencoded` with body and query string parameters?

Numerous services can accept query string parameters in the URL when a POST request is made with Content-Type: application/x-www-form-urlencoded and other parameters in the body, but it seems AWS API Gateway cannot while also accepting query string parameters.
When I call the AWS API Gateway with a POST Mapping Template for application/x-www-form-urlencoded and query string URL parameters (with a Lambda function), I get the following error:
{
"message":"When Content-Type:application/x-www-form-urlencoded,
URL cannot include query-string parameters (after '?'):
'/prod/webhook?inputType=wootric&outputType=glip&url=...'"
}
Here is an example cURL:
curl -XPOST 'https://{myid}.execute-api.{myregion}.amazonaws.com/prod/webhook? \
inputType=wootric&outputType=glip&url=https://hooks.glip.com/webhook/ \
11112222-3333-4444-5555-666677778888' \
-d "#docs/handlers/wootric/event-example_response-created.txt" \
-H 'Content-Type: application/x-www-form-urlencoded' -v
The specific goal is to get a Wootric webhook event posted to a Lambda function using a URL with query string parameters.
You can get the code here:
https://github.com/grokify/chathooks
The Wootric event body file is here:
https://raw.githubusercontent.com/grokify/chathooks/master/docs/handlers/wootric/event-example_response-created.txt
The GitHub issue is here:
https://github.com/grokify/chathooks/issues/15
The error message seems pretty definitive but I wanted to ask:
Is there a workaround to configure an API Gateway to support both?
Is there a standards-based reason why AWS would not support this or is this just a design decision / limitation?
If there's no solution to this, is there a good lightweight solution other than deploying a hosted server solution like Heroku. Also, do other cloud services support this with their API gateway + cloud functions, like Google?
Some examples showing support for both:
jQuery example: jQuery send GET and POST parameters simultaneously at AJAX request
C# example: Accessing query string variables sent as POST in HttpActionContext
Yes,there is a workaround and the key issue is to set the mapping template that will convert string into json . Very detailed example shown in
API Gateway any content type.
Please set the request property as "Content-Type", "application/json" for your HttpURLConnection like below
connection.setRequestProperty("Content-Type", "application/json");
I had a similar problem, with a 3rd party provider using web hooks. It turns out that my provider is transforming the url path from UPPERCASE to LOWERCASE. Example the endpoint should be apigateway.com/dev/0bscur3dpathRANDOM instead apigateway.com/dev/0bscur3dpathRANDOM. You get the point.
I'm not sure if I got the point in question correctly, but if you want to access the request body that is encoded as application/x-www-form-urlencoded(or anything, actually) in your Lambda function, you should use LAMBDA_PROXY request integration type (aka tick "Use Lambda Proxy integration" checkbox) when creating a method for your resource. Then you can access the request body in event.body field as a plain text in your lambda function and parse it manually.

Returning binary body and http headers from an AWS lambda through API gateway

I have a lambda that needs to return a binary object and some http headers (e.g. content-type) through an api gateway (using lambda integration) OR redirect to another URL. In the binary support examples (e.g. https://aws.amazon.com/blogs/compute/binary-support-for-api-integrations-with-amazon-api-gateway/) the lambda only returns the (base64 of the) binary object (the image). In my case, I also need to return a status code and http headers (or something equivalent). I struggle with how I can make this work with binary support in api gateway.
The lambda returns a json on this form:
{
"statusCode": 200,
"headers": {
"content-type": "image/jpeg"
},
"body": "/9j/4AAQS...gLDAoKCAwZK",
"isBase64Encoded": true
}
In the integration response I add body mappings for image/jpeg (etc) of the form:
$input.json('$.body')
And header mapping for 'content-type' like so:
integration.response.body.headers['content-type']
I've tried many variations of the above, but the result is consistently
Execution failed due to configuration error: Unable to transform response
How do I transform the json from the lambda into a form that can be converted to binary by the api gateway, with http headers and all? Can I get more debug logging out of the api gateway to show more specific what it is unhappy with?
Is there perhaps a way to get more debug logging out of the api gateway?
I recently got this working after facing a similar problem.
In my case, I was missing two things:
First, I needed to change the list of types AWS will send to the upstream in the "Accept" header"
"x-amazon-apigateway-binary-media-types" : [
"image/jpeg"
]
Secondly, I needed to set the Integration Response to "Convert to binary (if needed)":
"contentHandling": "CONVERT_TO_BINARY"
See this answer for the details, and sample config.
I also found that I wasn't being patient enough. Whenever I deployed the API, I was checking immediately, instead of waiting a few minutes for the changes to propagate.
I tired returning binary data with base64 encoding, but I couldn't manage to return it from Lambda function through API Gateway.
Therefore, I decided to redirect the URL. I changed the Method Response to 302 and added "Location" Response header. I also deleted response code 200 from Integration Response, selected 302 as response code and mapped integration.response.body.location value with Location header. My Lambda code returned location of redirect URL in this format:
{"location":"www.google.com/"}
Hope this helps.
I've been struggling to do the same for days, but couldn't find any documentation to back up that it's possible, instead I found this
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings-workflow.html
When converting a text payload to a binary blob, API Gateway assumes that the > text data is a Base64-encoded string and outputs the binary data as a
Base64-decoded blob. If the conversion fails, it returns a 500 response
indicating an API configuration error. You do not provide a mapping template
for such a conversion, although you must enable the passthrough behaviors on
the API.
When converting a binary payload to a text string, API Gateway always applies > a Base64 encoding on the binary data. You can define a mapping template for
such a payload, but can only access the Base64-encoded string in the mapping
template through $input.body, as shown in the following excerpt of an example > mapping template.
which sounds to me that mapping like this is only possible the other way around; what I ended up doing is that just return Base64Encoded binary string and hard code the content-type and cache- control in header mapping; for the complete implementation see my blog post https://mesfinmoges.com/dynamic-image-resizing-using-amazon-s3-aws-lambda-mazon-api-gateway-amazon-cloudfront/

Is it possible to add an HTTP header from AWS Custom Auth on API gateway?

I am using Custom Auth on AWS API Gateway, but I would like to add an extra HTTP header depending on the result. Does anyone know if this is possible, or how to do it. If it is not, is there an idea of if or when this will be possible?
Many thanks.
We recently added support for this. Docs should be up soon.
Now you can return an object like this from the authorizer function:
{
"principalId": "xxxxxxxx", // The principal user identification associated with the token send by the client.
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow|Deny",
"Resource": "arn:aws:execute-api:<regionId>:<accountId>:<appId>/<stage>/<httpVerb>/[<resource>/<httpVerb>/[...]]"
}
]
},
"context" : {
"key" : "value",
"numKey" : 1,
"boolKey" : true
}
}
Arrays and objects aren't allowed, only string/number/boolean as valid JSON. The root key must be named context.
You can access those values in the request $context like so:
$context.authorizer.key -> value
$context.authorizer.numKey -> 1
$context.authorizer.boolKey -> true
So to answer your question, you wont' be able to conditionally add the header, but you could set the header value to $context.authorizer.yourKey and if yourKey isn't set in the authorizer response, the header value would be blank (but the header would still be sent).
Edit:
Docs are live http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html
I was able to get this working after a day of pulling my hair out and hopefully I can save someone from that. It adds a bit more to Jacks' response.
Basically, you can dynamically add headers by using Body Mapping Templates
Create and Authorizer Lambda (you can use the authorizer blueprint lambda to get started), do your business logic to create the AuthPolicy and populate the context object with the key/values.
On the API Gateway, select the resource, click on Method Request and set the Auth to your Authorizer lambda
Open Method Execution, select the Integration type and make sure to unselect Use Lambda Proxy integration (if your request points to a lambda)
Add a Body Mapping Template - create one from the template and this is where you have access to $context.authorizer.key
Add the following to your template (just under "body-json" : $input.json('$')," is fine)
"headers": {
"key-header" : "$util.escapeJavaScript($context.authorizer.key)",
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))"
#if($foreach.hasNext),#end
#end },
This will add a new header called "key-header" and also forward all the original headers allowing you to append additional information like user_id, user_role, etc to the upstream service.
I tried Emanuel Canha's answer above, but that no longer seems to work. The way I got it to work yesterday (5 June 2019) is to
Create the lambda
Add the lambda under Method Request as your authorizer
Open Integration Request
Open HTTP Headers
Add a header with the name you want (key-header in the above example)
Use context.authorizer.yourKey as the mapped from entry. (Note that you do not use the $ in this field.)
You can only get PrincipalId from authorizer result, in your integration request, you can map a header value using context.authorizer.principalId
These answers seem to be specific to HTTP API Gateways. We're trying to user RestAPI API gateway which would work with both Lambdas (that take pricnipalID as part of the event object forwarded by Lambda Authorizer) but also, we would like to Authorize non-Lambda endpoints (deployed in private VPC and accessed via Network Load Balancer) so we want to put the principalId into HTTP headers the endpoint would parse.

How to pass through a query string parameter with square brackets in Amazon API Gateway

I want to use API Gateway as a caching proxy for an HTTP server, which in turn uses jsonapi-resources to define the API.
The issue is that jsonapi-resources requires query string parameters of the form ?page[number]=10&page[size]=10 to paginate results. However, if I try to add page[number] to URL Query String Parameters on a Method Request page, I receive the following error:
I've also tried to percent-encode the name as page%5Bnumber%5D without any success; the parameter is still filtered out.
Is there a way to make it work?
If you encode the [ by %5B, API Gateway doesn't allow % in the parameter name.
Currently, AWS API Gateway only supports the parameter name matches ^[a-zA-Z0-9._$-]+$
Does page.number work for you?

API Gateway CORS Issue

So I have CORS enabled going through the basic setup given by AWS Gateway. However for this API I need to allow Control Origins for all requests and allow credentials.
Here is what it looks like
The issue as you may have guessed is this setup is not allowed by CORS, you can not have a wildcard for Origin and have credentials as true. Normally the work around for this is to just grab the requesting domain and add it into the Origin Header. Which is more or less what I want to do. But I don't know how to get that information and add it as a mapping value. Where does API Gateway store that information and how do i get it?
UPDATE:
I have to pass through HTTP Header Host to my Lambda Function which I should have mentioned earlier, I have tried implementing the Answer below but I cannot access the header to pass it to the Lambda function using the instructions provided. Any more assistance with this is greatly appreciated.
Okay After hours of research and finding bits of information across the internet I have a solution and hopefully it is useful for other people.
To pass an HTTP Header that is not a default value provided by AWS API Gateway and then access that data via a Lambda Function and return that data in the Response Header follow the steps below
In "Method Request" go to "HTTP Request Headers" and add your desired header to capture. ie. if we want to get the host value of the API url you can enter "Host" here. if you want to get the website host of the caller use "Origin"
In "Integration Request" go to mapping templates and create a new template if an "application/json" does not exist, if it does just update it.
This is the important part, pass the header value you set in step 1. To do that write something similar to the following in the Template Box.
{
"origin" : "$input.params().header.Origin",
"host" : "$input.params().header.Host"
}
You can also pass in any url parameters you have defined in the same JSON.
Access the data from Lambda, The integration request passed the information into the "Event" parameter if using Node as the Lambda Backend code. to retrieve the value of any header just use the following within your handler.
event.origin;
When sending back your response from Lambda to API Gateway, it is best to format the response in JSON. Something similar to this.
{
"origin" : event.origin,
"host" : event.host,
"nonHeaderOutput" : "Hello World"
}
In "Integration Response" go to "Header Mappings", if the header you need is not listed you may add it in "Method Response" and it will then appear here. For this example I used "Access-Control-Allow-Origin" and edited the Mapping Value to be integration.response.body.origin
now go to "Mapping Templates and select the content type you want to use, and then edit the template to access the non header responses by adding this to the Template Box
$input.path("$.nonHeaderOutput")
That is it now the header that was sent to the API can be used in your method Response.
Jurgen from API Gateway here.
Thanks for reporting this issue. There is currently no simple way to set it up via the "Enable CORS" feature in the console. However, we are looking into it to improve the user experience for this use case.
As a potential workaround, you could passthrough the Origin header from the client to your backend and parse / create the value for the Access-Control-Allow-Origin header there. Then you would map the Header in the Integration Response from 'integration.response.header.Access-Control-Allow-Origin' to Access-Control-Allow-Origin and return it to the client.
Hope this helps.
Best,
Jurgen