Hello there,
currently I am trying to serve an image from a lambda function as a binary response.
That is working very well as I specified
BinaryMediaTypes:
- "*/*"
On my RestApi Cloudformation template.
However, now I am trying to test that api locally with the help of sam local and found no way to define the aformentioned parameter in the sam.yaml file.
Can you assist with the correct setup for serving binary responses with sam local?
Thanks
This was merged recently in #250 - should go out with the next release.
Haha ohwell, that is convenient - will have a look and reopen this if needed.
Thanks for the info!
I've had a look through the changes and the tests and tried to come up with a configuration for it:
sam version: 0.27
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Resources:
LocalApi:
Type: AWS::Serverless::Api
Properties:
StageName: dev
DefinitionBody:
swagger: "2.0"
paths:
"/transform":
get:
x-amazon-apigateway-integration:
httpMethod: "GET"
type: aws_proxy
uri: "https://hopefullyIrellevant.com"
x-amazon-apigateway-binary-media-types:
- "*/*"
Imageprocessing:
Type: AWS::Serverless::Function
Properties:
Handler: imageprocessing.transform
Runtime: nodejs6.10
Timeout: 100
Events:
Api:
Type: Api
Properties:
Path: /transform
Method: get
RestApiId:
Ref: LocalApi
The response object in my lambda looks like this:
const transformedResponse = {
statusCode: HttpStatus.OK,
body: result.toString('base64'),
isBase64Encoded: true,
headers: {
[HttpHeader.CONTENT_TYPE]: 'image/*'
}
};
Using this configuration in the sam.yaml file, sam local starts and an invocation to /transform delivers the transformed response - however as a base64 encoded string. I was expecting it to be binary data, since I've configured x-amazon-apigateway-binary-media-types.
Hey @ferencbeutel, that is indeed the expected behavior. Lambda can only receive a JSON payload, it does not understand binary data. For this reason, when you configure binary content types in API Gateway, the request payload is base64 encoded by API Gateway and passed on to Lambda as a base64 string in the body field. If you use frameworks such as serverless-java-container or serverless-express, the data is decoded automatically and passed to your code as if it was the original binary payload. If you don't use any frameworks, you will have to handle this use-case yourself.
hey @SAPessi, sorry for making it unclear that I was talking about the response, not the request payload. When I configure my api gateway in my amazon aws account, I can set the BinaryMediaTypes option, which automatically decodes the base64 response my lambda function returns into binary data. I was expecting to replicate that api gateway behaviour with sam local somehow.
this should also work with SAM local. You should only have to set the isBase64Encoded property in the proxy response: https://github.com/awslabs/aws-sam-local/pull/250/files/a2088980ce7c631f18fd602c97b457ef419fb985#diff-c664288ff884b387c4ab4fc452e93c31R598
Does that not work?
Hey @SAPessi, I am afraid that I cannot get it to return binary data. For further investigation, I've provided a simple testing setup.
As you can see, There are 2 lambda functions, which both read an image into memory and then return it, one time with the isBase64Encoded flag and one time without it. Running this setup with sam local and then curling both endpoints provides me with exactly the same result, both times with a base64 encoded string.
EDIT: My bad, I wasnt aware of the fact that one has to match the Content-Type exactly with the Accept header. Doing so provides me with the decoded response. Thanks for your patience :)
Sorry for reopening this once again, but the implemented logic in sam local sadly does not match amazon api gateways logic. On the api gateway, It is possible to configure the media types, which should be encoded/decoded by the gateway, in the BinaryMediaTypes field. If the Accept-Header of the request or the content-type header of the response matches one of the values configured in the binaryMediaTypes option of the api gateway, the encoding/decoding is done. I've got this information from here.
Why is this important? Think about a usecase where a lambda function is used to manipulate images, for example by resizing them. Those links could be inserted directly into the src-Attribute of \Accept Header, which makes it impossible atm to use sam local in this scenario, since you are strictly matching the first accept header value with the content-type header.
It would be great to have some form of configurability over the content-types leading to a decoding of the response, just like in the amazon api gateway.
Thanks for filing this issue, I am having the exact same problem where I can not use SAM local to develop my image manipulation lambda. I can curl my local resources with an -H "Accept: image/gif" and get the data back correctly but not without it.
To get around this in local dev and allow in browser view of the images is to create a base64 CSS'ed representation that mimics what the browser would do for a raw image load in a window. Write your own condition code that calls this.
const imgData = `data:image/jpg;base64,${data}`;
return `<body style="
margin: 0;
width: 100vw;
height: 100vh;
background-image: url(${imgData});
background-size: contain;
background-repeat: no-repeat;">
</body>`
Likewise, for local dev I return text/html headers. However, I would very very much like local dev to be what I see when this is deployed so I can QA the header requirements or use the lambda locally as a src in other local work.
SAM CLI latest version (v0.3.0) now supports the same functionality that APIGW supports for binary media types. Closing this issue. Feel free to re-open if it doesn't work in latest version.
Thanks for the note, but I could not confirm this because #457 blocked my upgrade.
Hi @sanathkr thats great to hear! I couldnt find any documentation about this, could you elaborate a little bit about how to configure the BinaryMediaTypes value?
I can confirm this works. Thanks y'all.
For anyone ending up here trying to make it work, and wondering exactly what the solution was.
By adding this to my template.yaml
Globals:
Api:
BinaryMediaTypes:
# These are equivalent to image/gif and image/png when deployed
- image~1gif
- image~1png
I was able to return a gif image without having to set the Accept header in the request. And it seems that adding - "*~1*" works as well.
x-amazon-apigateway-binary-media-types:
- "*/*"
Worked just fine for me, but mine serves any binary data format.
Hello,
I came across a strange issue when trying to add Binary Data Types to my template.yaml file. This issue is also mentioned in 'Images not served properly' here :
https://github.com/awslabs/aws-serverless-express/issues/104
, but that thread was closed with no solution. The issue is that the Accept header needs to exactly match the Binary Data Type before correctly displaying the image, otherwise it would just return the base64 string.
I am setting the binary data type as follows in the SAM template.yaml:
`Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
Name: MyApi
StageName: !Ref Stage
BinaryMediaTypes:
- image~1*`
I have a lambda function which returns a jpeg image as base64 in the lambda handler as follows:
` obj = s3.Object(bucket_name, item_name)
img = obj.get()['Body'].read()
img_base64 = base64.b64encode(img).decode('utf-8')
return {
'statusCode': 200,
'headers': {
"Access-Control-Allow-Origin": "*",
"Content-type": "image/jpeg"
},
'body': img_base64,
'isBase64Encoded': True
}`
When testing the api locally using sam local strart-api, it works perfectly regardless of the request Access header value. But when I deploy the API, I can only get the correct behaviour if the request Access header is set to 'image/*'. If It is not set then the base64 is returned instead. Why is this and is there any workaround?
This totally does not work, whatever you do.
Most helpful comment
For anyone ending up here trying to make it work, and wondering exactly what the solution was.
By adding this to my template.yaml
I was able to return a gif image without having to set the Accept header in the request. And it seems that adding
- "*~1*"works as well.