6

I've set up AWS API Gateway to pass through requests to a service that returns images.

The endpoint as it appears in the AWS UI

When I use the "Test" functionality in the UI, the logs show the PNG data being returned in the method response, as well as the `Content-Type=image/png:

The AWS log of the "Test" UI showing Content-Type being correct

However, when you actually go and visit the endpoint in a browser, the Content-Type is application/json. I would have expected that the "Method response headers" displayed in the logs of the "Test" UI to match what would actually be returned.

How do I force API Gateway to return the upstream's Content-Type (image/png in this case, but others more generally) to the browser?

Here is the endpoint as defined in the Swagger 2.0 syntax:

"/format/{id}/image.png": {
  "get": {
    "tags": [],
    "summary": "",
    "deprecated": true,
    "operationId": "get-png",
    "produces": [
      "image/png"
    ],
    "parameters": [
      {
        "name": "id",
        "in": "path",
        "description": "My Description",
        "required": true,
        "type": "string"
      }
    ],
    "responses": {
      "200": {
        "description": "Successful operation",
        "schema": {
          "type": "file"
        },
        "headers": {
          "Access-Control-Allow-Origin": {
            "type": "string",
            "description": "URI that may access the resource"
          },
          "Content-Type": {
            "type": "string",
            "description": "Response MIME type"
          }
        }
      }
    },
    "x-amazon-apigateway-integration": {
      "responses": {
        "default": {
          "statusCode": "200",
          "responseParameters": {
            "method.response.header.Access-Control-Allow-Origin": "'*'",
            "method.response.header.Content-Type": "integration.response.header.Content-Type"
          }
        }
      },
      "requestParameters": {
        "integration.request.path.id": "method.request.path.id"
      },
      "uri": "https://[image_service]/{id}.png",
      "passthroughBehavior": "when_no_match",
      "httpMethod": "GET",
      "type": "http"
    }
  }
}

Notes:

  • This endpoint is somewhat simplified (but still illustrates the problem). However in reality, there is more to the endpoint (ie. I'm not just proxying the requests, but also rewriting paths + query params).
    • As noted in this answer, if your endpoint is just proxing requests to an image server, you should probably use AWS CloudFront instead. It has edge caching included in its price, and is ~3x cheaper.
movermeyer
  • 1,502
  • 2
  • 13
  • 19

2 Answers2

5

It turned out that 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/png"
]

Secondly, I needed to set the Integration Response to "Convert to binary (if needed)":

"contentHandling": "CONVERT_TO_BINARY"

Here is the modified configuration:

{
  "swagger": "2.0",
  "info": {
    "description": "My description",
    "title": "My Title",
    "version": "1.0.0"
  },
  "schemes": [
    "https",
    "http"
  ],
  "paths": {
    "/format/{id}/image.png": {
      "get": {
        "tags": [],
        "summary": "My Description",
        "deprecated": true,
        "operationId": "get-png",
        "produces": [
          "image/png"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "",
            "required": true,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "schema": {
              "type": "file"
            },
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string",
                "description": "URI that may access the resource"
              },
              "Content-Type": {
                "type": "string",
                "description": "Response MIME type"
              }
            }
          }
        },
        "x-amazon-apigateway-integration": {
          "responses": {
            "default": {
              "statusCode": "200",
              "responseParameters": {
                "method.response.header.Content-Type": "integration.response.header.Content-Type",
                "method.response.header.Access-Control-Allow-Origin": "'*'"
              },
              "contentHandling": "CONVERT_TO_BINARY"
            }
          },
          "requestParameters": {
            "integration.request.path.id": "method.request.path.id"
          },
          "uri": "https://img.shields.io/pypi/format/{id}.png",
          "passthroughBehavior": "when_no_match",
          "httpMethod": "GET",
          "type": "http"
        }
      }
    }

  },
  "definitions": {},
  "x-amazon-apigateway-binary-media-types" : [
    "image/png"
  ]

}
movermeyer
  • 1,502
  • 2
  • 13
  • 19
0

If you want to rewrite paths/query params and/or HTTP responses, you could use AWS lambda to listen for "client response" events coming from your upstream web server, and set the final HTTP response headers, etc. in there.

tez
  • 574
  • 6
  • 13
  • In this case, the response is coming back from the upstream with the correct headers, and I have added the mapping to allow them through. But the headers shown in the test UI don't match the headers the browser actually gets back from the Gateway. No need for Lambda here, nor any indication that it would have any difference on the result if you just hooked it up between "Integration Response" and "Method Response" – movermeyer Mar 20 '18 at 17:45