AWS Compute Blog
Handling binary data using Amazon API Gateway HTTP APIs
This post is written by Rudolf Potucek, Startup Solutions Architect.
Amazon API Gateway REST APIs have supported binary data since 2016. API Gateway HTTP APIs makes it easier to work with both binary and text media types. It supports a new payload format version and infers encoding based on request and response formats. In this post, I show how to use HTTP APIs and AWS Lambda to build an API that accepts and returns either text or images.
API Gateway’s base64-encoding unifies the handling of text and binary data in Lambda. Binary and non-binary data is passed to a Lambda function as a string in a JSON object. The HTTP API Lambda integration automatically infers the need for encoding based on the content-type
header passed with the request.
When using curl to pass a plaintext object:
curl -X POST -H 'content-type: text/plain' --data-binary "Hello World" $ECHO_JSON_API | jq .
Lambda receives the following:
{
...
"body": "Hello World",
"isBase64Encoded": false
}
When passing a binary object:
curl -X POST -H 'content-type: image/jpeg' --data-binary @../testdata/rainbow-small.jpg $ECHO_JSON_API | jq .
Lambda receives the following:
{
...
"body": "/9j/4AAQSkZ...",
"isBase64Encoded": true
}
A Lambda function can inspect the isBase64Encoded
flag and reverse the encoding to obtain the original data.
For the response path, API Gateway inspects the isBase64Encoding
flag returned from Lambda.
The following response from Lambda results in the corresponding text “base64 encoded”:
{
"statusCode": 200,
"body": "YmFzZTY0IGVuY29kZWQK",
"isBase64Encoded": true
}
The following response from Lambda results in “plaintext”:
{
"statusCode": 200,
"body": "plain text",
"isBase64Encoded": false
}
The content-type is text/plain
in both cases.
Showing how HTTP APIs handle binary data
This example uses AWS Serverless Application Model (AWS SAM) to show how HTTP APIs with Lambda integration handle binary and text data. The code is available in GitHub.
To follow the example, you need a git client, AWS SAM CLI (minimum version 0.48), and an AWS account:
- Create an AWS account if you do not already have one and login.
- Clone the repo to your local development machine:
- Build the function dependencies
- Deploy the AWS resources specified in the template.yaml file.
- During the prompts, enter the stack name
http-api
and enter the AWS Region for deployment. Accept the defaults for the remaining questions, answering Y to the three “function may not have authorization defined” questions. - Assign the values of the API outputs to the following environment variables, in this post’s example:
git clone https://github.com/aws-samples/handling-binary-data-using-api-gateway-http-apis-blog.git
cd aws-samples/handling-binary-data-using-api-gateway-http-apis-blog/sam-code
sam build --use-container
sam deploy --guided
Once deployed, AWS SAM outputs the locations of three APIs.
ECHO_RAW_API=https://99mx7f1kg0.execute-api.us-east-1.amazonaws.com/prod/echoraw
ECHO_JSON_API=https://99mx7f1kg0.execute-api.us-east-1.amazonaws.com/prod/echojson
NOISE_API=https://p5kbm51wm7.execute-api.us-east-1.amazonaws.com/prod/noise
Exploring the API
The AWS SAM template defines an HTTP API with a /noise endpoint that forwards ANY HTTP request to a Lambda function. Selection logic routes the request within the single function handler. Separate functions could be created to separate GET and POST functionality. This example uses a single handler to mimic the behavior of frameworks such as FastAPI and Mangum.
The Lambda function uses the Python Pillow library to generate and process images. It accepts query string parameters to define an image to generate. The function also accepts a query string parameter flag called demo64Flag
to specify how to return text data and an Accept
header to define the mime type or default to JPEG.
Once deployed, the Lambda function performs the following steps depending on the route. A GET request generates an image and a POST request overlays noise on an uploaded image.
HTTP GET to generate JPEGs
- Browse to the NoiseHttpApi URL that AWS SAM outputted. The HTTP API endpoint forwards the request to the Lambda function, which routes it as a
GET
request. The Pillow library converts some random noise and generates a binary JPEG image, resulting in a large horizontal rectangle filled with noise. - Append the following to the URL:
?w=50&h=100&min=96&max=192
- Use curl to add the
Accept
header and download a GIF image. - Use file to confirm the returned file is of type GIF.
- Use curl to request an unknown image format and return a plaintext answer.
- Use curl to request an unknown image format and return a base64 encoded answer.
The Lambda function checks if any query string parameters have been passed. The code accepts image height and width, in addition to min
and max
brightness of noise pixels.
This requests a noise image 50 pixels wide, 100 pixels high, with brightness from 96-192/255, which results in a smaller vertical rectangle filled with noise.
Based on the Accept header the Lambda function sets the output image format.
# Ask for GIF
curl "${NOISE_API}?w=50&h=50" --output test-get.gif -H 'Accept: image/gif'
file test-get.gif
test-get.gif: GIF image data, version 87a, 50 x 50
The Lambda function also accepts a query string parameter called demo64Flag
. This specifies how to return text data when asked for an unknown image type via the Accept header.
By invoking with demo64Flag=0
, the code returns a plaintext string Text path: Unknown encoding requested
. This tells API Gateway that the string is of type text/plain
and not base64 encoded.
curl "${NOISE_API}?demo64Flag=0" -H 'Accept: image/unknown'
Text path: Unknown encoding requested
Setting demo64Flag=1
, the code returns QmluYXJ5IHBhdGg6IFVua25vd24gZW5jb2RpbmcgcmVxdWVzdGVk
. This is the base64 encoded equivalent of the string Binary path: Unknown encoding requested
, telling API Gateway that the string is of type text/plain
and is base64 encoded.
The HTTP API endpoint uses the isBase64Encoded
flag to decode the text before returning the response to the caller.
curl "${NOISE_API}?demo64Flag=1" -H 'Accept: image/unknown'
Binary path: Unknown encoding requested
The HTTP API endpoint has converted the base64 encoded string back into text.
HTTP POST to generate overlay noise
The following examples show how to handle both text and binary media types on the same API. The Lambda function POST
path accepts images and adds some noise. It also allows uploading text and rendering it to an image before applying noise to it
The repo contains a test image rainbow-small.jpg and a test text file multiline.txt.
- Use curl to upload the JPEG rainbow image, overlay noise, and request a GIF file.
- Use file to confirm that the returned file is of type GIF.
- View the resulting image to see the generated noise.
- Use curl to upload the text file, render to an image, overlay noise, and request a GIF file. The text file is uploaded with the –data-binary flag to ensure that curl does not remove newlines.
- Use file to confirm that the returned file is of type GIF.
- View the resulting image to see the generated noise.
curl "${NOISE_API}" -X POST --data-binary @../testdata/rainbow-small.jpg -H 'content-type: image/jpeg' -H 'Accept: image/gif' --output test-post-image.gif
$ file test-post-image.gif
test-post-image.gif: GIF image data, version 87a, 100 x 100
curl "${NOISE_API}?w=100&h=100" -X POST --data-binary @../testdata/multiline.txt -H 'content-type: text/plain' -H 'Accept: image/gif' --output /tmp/test-post-text.gif
$ file test-post-text.gif
test-post-text.gif: GIF image data, version 87a, 100 x 100
For both examples, the HTTP API endpoint received an unaltered binary file, using the --data-binary
flag. Based on the content-type
header, the API understands it is a binary file, base64 encodes it, and notifies the Lambda code with the isBase64Encoded
flag. The Lambda function receives the data and base64 decodes it if necessary. It reads the uploaded image or renders text to a new image, and finally adds noise.
CloudWatch Logs confirms that the text is received without being base64 encoded.
... 'body': 'Lorem ipsum dolor sit amet, \nconsectetur adipiscing elit,...', 'isBase64Encoded': False ...
Supporting CORS for browser calls
To show the functionality using a static webpage, the HTTP APIs endpoint must accept CORS requests from anywhere. This is enabled by the CorsConfiguration
statement of the HTTP API within the AWS SAM template. The Lambda function must also support the OPTIONS
verb and return an Access-Control-Allow-Origin
header in the response data.
The example uses a static webpage containing HTML and JavaScript. It lets you draw something with your pointer and makes a fetch()
call to the API to overlay some noise once you choose the submit button.
Test this by browsing to this GitHub pages project page. On the page, draw in the top-left box to generate some image data. Enter the NOISE_API
URL in the API URL and choose Submit. The Lambda function receives the image data via the HTTP API POST. The function generates the noise and returns the binary file to the client resulting in an image similar to the following:
Conclusion
API Gateway HTTP APIs makes it easier to work with both binary and text media types. HTTP API Lambda integration can automatically infer the need for encoding based on the content-type
header passed with the request. Passing an isBase64Encoded
boolean value with the data simplifies the encoding and decoding of binary data.
HTTP APIs also make it easier to use CORS to provide domain name-based access controls to APIs. This enables use cases such as calling an API from statically hosted sites.
For more serverless learning resources, visit https://serverlessland.com.