claudiajs / claudia-api-builder

Use AWS API Gateway as if it were a lightweight JavaScript web server

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unable to use ApiResponse to return readable errors when serving binary content

mattdelsordo opened this issue · comments

  • Expected behaviour:

Say for example, you have a lambda that serves dynamic images, but in the case that some part of the process fails you want to be able to respond with a context-specific status code (which is something I'm looking at doing). It seems like you should be able to use ApiResponse objects to dynamically determine whether you return something like
new api.ApiResponse(buffer, { 'Content-Type': 'image/png' }, 200)
on success or
new api.ApiResponse(error.toString(), {'Content-Type': 'text/plain' }, error.status)
on an error.

  • What actually happens:

You don't seem to be able to set the { success: { contentHandling: 'CONVERT_TO_BINARY' } } flag inside an ApiResponse object which leads to all content being converted to binary, including the errors. The HTTP status is fine, but the error message is unreadable nonsense. If you leave that off the declaration of the endpoint, images obviously don't get converted to binary, although errors then are fine. It looks like ApiResponses that handle error status codes aren't treated like actual errors, and still trigger flags in the "success" object. Looking at the source code in src/api-builder seems to confirm this:

} else if (isApiResponse(modifiedRequest)) {
return context.done(null, packResult(modifiedRequest, getRequestRoutingInfo(request), {}, 'success'));

packResult being where

if (configuration && configuration.contentHandling === 'CONVERT_TO_BINARY' && resultType === 'success') {
result.isBase64Encoded = true;
}

happens.

At any rate, it seems like there should be some sort of way to either set contentHandling: 'CONVERT_TO_BINARY' in a specific ApiResponse object to to not treat ApiResponses like successes universally.

  • Link to a minimal, executable project that demonstrates the problem:

Something along the lines of this should do the trick:

import APIBuilder from 'claudia-api-builder'
import fs from 'fs'
import path from 'path'

const API = new APIBuilder()

API.get('/{x}', (req) => {
    const param = req.pathParams.x

    if (isNaN(x)) return new API.ApiResponse('Error: x is meant to be a number.', { 'Content-Type': 'text/plain' }, 400)

    const img = fs.readFileSync(path.join(__dirname, './img.png'))
    return new API.ApiResponse(img, { 'Content-Type': 'image/png' }, 200)

}, { success: { contentHandling: 'CONVERT_TO_BINARY' }})
  • Steps to install the project:
    Run yarn install and deploy the project to AWS.

  • Steps to reproduce the problem:

Access the endpoint using a number and the image should be served fine, but doing so with a string should get you a 400 status and an unreadable buffer.
Caveat: AWS API Gateway has issues serving images if you navigate to the endpoint but you'll be able to curl it with -H "Accept: image/png"

Unfortunately this is a limit of API Gateway, an integration (=call to Lambda from API GW) can only have a single 'content-handling' method, so Claudia won't be able to override it for individual responses. Check out https://docs.aws.amazon.com/cli/latest/reference/apigateway/put-integration.html

Try throwing a string or an ApiResponse object out of your method instead of returning it, it might do the trick. That will cause the error template to trigger, not the success template.

After some more testing/thought it actually looks like this isn't a huge issue at all - the problem was with how I was testing things locally. After testing a deployed version of my project, AWS seems to work out content encoding just fine.