Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for serving binary responses #312

Closed
ferencbeutel4711 opened this issue Feb 22, 2018 · 20 comments
Closed

Support for serving binary responses #312

ferencbeutel4711 opened this issue Feb 22, 2018 · 20 comments

Comments

@ferencbeutel4711
Copy link

ferencbeutel4711 commented Feb 22, 2018

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

@sapessi
Copy link
Contributor

sapessi commented Feb 23, 2018

This was merged recently in #250 - should go out with the next release.

@ferencbeutel4711
Copy link
Author

Haha ohwell, that is convenient - will have a look and reopen this if needed.
Thanks for the info!

@ferencbeutel4711
Copy link
Author

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.

@sapessi
Copy link
Contributor

sapessi commented Feb 26, 2018

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.

@ferencbeutel4711
Copy link
Author

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.

@sapessi
Copy link
Contributor

sapessi commented Feb 26, 2018

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?

@ferencbeutel4711
Copy link
Author

ferencbeutel4711 commented Feb 26, 2018

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 :)

@ferencbeutel4711
Copy link
Author

ferencbeutel4711 commented Feb 26, 2018

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 <img> tags. But sadly, every browser sends a different 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.

@metaskills
Copy link
Contributor

metaskills commented Mar 29, 2018

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.

@metaskills
Copy link
Contributor

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>`

@metaskills
Copy link
Contributor

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.

@sanathkr
Copy link
Contributor

sanathkr commented Jun 1, 2018

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.

@sanathkr sanathkr closed this as completed Jun 1, 2018
@metaskills
Copy link
Contributor

Thanks for the note, but I could not confirm this because #457 blocked my upgrade.

@ferencbeutel4711
Copy link
Author

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?

@metaskills
Copy link
Contributor

I can confirm this works. Thanks y'all.

@christian-boks
Copy link

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.

@metaskills
Copy link
Contributor

x-amazon-apigateway-binary-media-types:
  - "*/*"

Worked just fine for me, but mine serves any binary data format.

@WianSnyman
Copy link

WianSnyman commented May 26, 2020

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 :
CodeGenieApp/serverless-express#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?

@davidmold
Copy link

This totally does not work, whatever you do.

@marinaglancy
Copy link

marinaglancy commented Jan 23, 2021

It works with '*/*' , you just need to add the content-type header to every response. However this does not work together with CORS and it really sucks, see aws/serverless-application-model#1344

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants