29

I have created a lambda function using serverless. This function is fired via API Gateway on a GET request and should return a pdf file from a buffer. I'm using html-pdf to create the buffer and trying to return the pdf file with the following command

  let response = {
    statusCode: 200,
    headers: {'Content-type' : 'application/pdf'},
    body: buffer.toString('base64'),
    isBase64Encoded : true,
  };
  return callback(null, response);

but the browser is just failing to load the pdf, so I don't know exactly how to return the pdf file directly to the browser. Could'nt find a solution for that.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
sami_analyst
  • 1,751
  • 5
  • 24
  • 43
  • 1
    I'm curious about the Base64 encoding. Is that necessary? Mayb this is the problem? I'd return the data in binary format. – C-Otto Jul 27 '17 at 10:48
  • I tried some variations (also without bas64 encoding). The suggestion came from the following link : https://github.com/serverless/serverless/issues/2797 – sami_analyst Jul 27 '17 at 10:51

6 Answers6

41

well, I found the answer. The settings in my response object are fine, I just had to manually change the settings in API Gateway for this to work in the browser. I have added "*/*" to binary media types under the binary settings in API Gateway console

API GATEWAY

  1. just log into your console
  2. choose your api
  3. click on binary support in the dropdown
  4. edit binary media type and add "*/*"

FRONTEND

opening the api url in new tab (target="_blank"). Probably the browser is handling the encoded base 64 response, In my case with chrome, the browser just opens the pdf in a new tab exactly like I want it to do.

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
sami_analyst
  • 1,751
  • 5
  • 24
  • 43
  • 2
    Can you share 1) exactly what changes were done on the API Gateway 2) How are you handling base64 encoded response string on client ? – Shuki Jul 28 '17 at 10:45
  • @Shuki just updated the answer, hope this also works for you – sami_analyst Jul 28 '17 at 14:07
  • @sami_analyst this worked for me so thanks, weird that 'application/pdf' isn't working when I upload a pdf. – Christopher Grigg Sep 28 '17 at 17:11
  • 2
    I set the binary media type to "application/pdf" and also ensured that the API Gateway - Method Response included an HTTP Status for "200" with a response body Content-type of "application/pdf" (Empty model). Plus a HTTP Status for "500" with a response body Content-type of "text/plain" (Empty model), for when an error occurs. – jpblancoder Jul 18 '18 at 14:50
  • This works only if your output PDF is less than 6MB. – Noel Llevares Jul 19 '18 at 03:56
  • @jpblancoder You saved my life with the "200 status with empty mode with application/pdf Content-Typel" in the Method Response definition. I wish i could upvote your comment twice. Thanks alot – Frederik Nygaard Svendsen Sep 24 '18 at 13:22
  • @FrederikNygaardHavlundSvend its not working for me, can you please tell me waht might be the issue ? I get a base64 string from lambda integration and then I have added application/pdf to method response difination for 200 status, but I am still getting the text/plain response – Haris Mehmood Sep 25 '18 at 08:59
  • 2
    @HarisMehmood I separated the wkhtmltopdf into its own lambda function and have a dotnet core webapi invoking that lambda function to get the base64 encoded string. I then UTF8 Decode that string to a byte[] and return that as a file from the WebApi. My issue, was that the response from the webapi was an invalid pdf file. this was fixed after i added the following binary media type in the settings page on the apiGateway: \*/\* as well as adding a 200 OK method response with content type "application/pdf" and "Empty" model. And re deployed the API Gateway. Let me know that fixes it for you – Frederik Nygaard Svendsen Sep 26 '18 at 09:42
  • @dashmug Have u tried with more then 6MB? Will it work? – Prashant Tapase Apr 25 '19 at 06:48
  • @PrashantTapase It won't. Lambda's output is limited to 6MB. You'll get an error saying "body size is too long". – Noel Llevares Apr 25 '19 at 10:35
  • is it working multiple content type? @FrederikNygaardHavlundSvend have u tried for multiple content type? – Prashant Tapase Apr 26 '19 at 14:22
  • Thank you for sharing your experiences. While this answers your own question partially, this should not be the accepted answer, because you did not solve it in *Serverless Framework* as tagged in your question. – Mohammed Noureldin Mar 14 '21 at 23:29
18

After spending several hours on this I found out that if you set Content handling to Convert to binary (CONVERT_TO_BINARY) the entire response has to be base64, I would otherwise get an error: Unable to base64 decode the body.

Therefore my response now looks like:

callback(null, buffer.toString('base64'));

The Integration response:

enter image description here

The Method response:

enter image description here

And Binary Media Types:

enter image description here

teocomi
  • 874
  • 1
  • 11
  • 21
  • 2
    In case you go the SAM way, add below lines `Globals: Api: BinaryMediaTypes: - application~1pdf ` – Chinh Nguyen Aug 14 '19 at 17:47
  • You can add the binary media type in serverless framework, you can see here [Binary Media Type responses](https://serverless.com/blog/framework-release-v142/) – Alex Montoya Apr 18 '20 at 00:07
7

If you have a gigantic PDF, then it will take a long time for Lambda to return it and in Lambda you are billed per 100ms.

I would save it to S3 first then let the Lambda return the S3 url to the client for downloading.

Noel Llevares
  • 15,018
  • 3
  • 57
  • 81
1

I was having a similar issue where pdf where downloaded as base64 and started happening when changed the serverles.yml file from:

binaryMediaTypes:
      - '*/*'

to

binaryMediaTypes:
      - 'application/pdf'
      - '....other media types'

The issue is because the way AWS implemented this feature. From aws documentation here:

When a request contains multiple media types in its Accept header, API Gateway honors only the first Accept media type. If you can't control the order of the Accept media types and the media type of your binary content isn't the first in the list, add the first Accept media type in the binaryMediaTypes list of your API. API Gateway handles all content types in this list as binary.

Basically if the first media type contained in the accept request header is not in your list in binaryMediaTypes then you will get base64 back.

I checked the request in the browser and the first media type in the accept header was text/html so I got it working after changing my settings to:

binaryMediaTypes:
          - 'application/pdf'
          - '....other media types'
          - 'text/html'

Hope this helps anyone with the same issue.

Goca
  • 1,743
  • 2
  • 15
  • 36
0

Above solution is only for particular content-type. You can't more content type. Follow only below two-step to resolve multiple content type issue.

  1. Click on the checkbox of Use Lambda Proxy integration

API gateway --> API --> method --> integration request

enter image description here

enter image description here

  1. Create your response as

        let response = {
    
          statusCode: 200,
          headers: {
            'Content-type': 'application/pdf',//you can change any content type
            'content-disposition': 'attachment; filename=test.pdf' // key of success
          },
          body: buffer.toString('base64'),
          isBase64Encoded: true
        };
        return response;
    

Note* - It is not secure

Máté
  • 2,294
  • 3
  • 18
  • 25
Prashant Tapase
  • 2,132
  • 2
  • 25
  • 34
0

Instead of doing all this. It's better to use serverless-apigw-binary plugin in your serverless.yaml file.

Add

plugins:
- serverless-apigw-binary

custom:
   apigwBinary:
    types:
      - "application/pdf"

Hope that will help someone.

Farukh Khan
  • 199
  • 5
  • 21