¿Existe alguna forma de cambiar los códigos de estado http devueltos por Amazon API Gateway?

95

Por ejemplo, si quiero devolver un error 400 específico para parámetros no válidos o quizás un 201 cuando la llamada a la función lambda dio como resultado un create.

Me gustaría tener diferentes códigos de estado http, pero parece que la puerta de enlace api siempre devuelve un código de estado 200 incluso si la función lambda devuelve un error.

MonoBonkey
fuente
2
por lo que parece que el problema que estaba teniendo era que estaba devolviendo un tipo de error personalizado, lo que hace que la expresión regular errorMessage no funcione correctamente. Devolver una cadena estándar en la respuesta de falla de lambda hará que funcione la siguiente solución; sin embargo, devolver su propio objeto de error personalizado no lo hará.
MonkeyBonkey
mi solución fue cambiar de la versión 0.5 a la 1.0 sin servicio. Además, estoy usando la respuesta de la documentación de Serveless, especificando el statusCode en el objeto de respuesta como una propiedad. Espero que ayude
Relu Mesaros

Respuestas:

79

Actualización por 20-9-2016

Amazon finalmente hizo esto fácil usando la integración de Lambda Proxy . Esto permite que su función Lambda devuelva códigos HTTP y encabezados adecuados:

let response = {
    statusCode: '400',
    body: JSON.stringify({ error: 'you messed up!' }),
    headers: {
        'Content-Type': 'application/json',
    }
};

context.succeed(response);

¡Diga adiós a la asignación de solicitudes / respuestas en API Gateway!

opcion 2

Integre una aplicación Express existente con Lambda / API Gateway utilizando aws-serverless-express .

Eric Eijkelenboom
fuente
No puedo integrarlo, quiero decir, obtengo el estado 200 y la respuesta creada (el error creado). Me estoy perdiendo de algo ? ¿Cómo se ve "s-function.json"?
Relu Mesaros
Para obtener el ejemplo más simple, consulte el modelo Lambda de AWS llamado microservice-http-endpoint (en la consola de AWS Lambda). Mencionas "s-function.json", que parece que estás usando el marco Serverless ( serverless.com ). Esta es otra bestia por completo y no debe confundirse con aws-serverless-express o Lambda / API Gateway 'sin procesar'. Mi respuesta no describe cómo hacer que esto funcione usando el marco sin servidor.
Eric Eijkelenboom
7
Para cualquiera que se pregunte, esto también se puede lograr utilizando el nuevo callbackestilo. Solo hazlo callback(null, {statusCode: 200, body: 'whatever'}).
Widdershin
1
@Sushil sí, solo devuelve el JSON como en la variable de respuesta anterior.
unclemeat
8
@Sushil He resuelto esto en Python con LambdaProxyIntegration y regresandoreturn { "isBase64Encoded": True, "statusCode": 200, "headers": { }, "body": "" }
Jithu R Jacob
74

Esta es la forma más rápida de devolver códigos de estado HTTP personalizados y uno personalizado errorMessage:

En el panel de API Gateway, haga lo siguiente:

  1. En el método de su recurso , haga clic en la respuesta del método
  2. En la tabla de estado HTTP , haga clic en agregar respuesta y agregue cada código de estado HTTP que desee utilizar.
  3. En el método de su recurso , haga clic en respuesta de integración
  4. Agregue una respuesta de integración para cada uno de los códigos de estado HTTP que creó anteriormente. Asegúrese de que el paso de entrada esté marcado. Use la expresión regular de error lambda para identificar qué código de estado debe usarse cuando devuelva un mensaje de error de su función lambda. Por ejemplo:

    // Return An Error Message String In Your Lambda Function
    
    return context.fail('Bad Request: You submitted invalid input');
    
    // Here is what a Lambda Error Regex should look like.
    // Be sure to include the period and the asterisk so any text
    // after your regex is mapped to that specific HTTP Status Code
    
    Bad Request: .*
    
  5. Su ruta de API Gateway debería devolver esto:

    HTTP Status Code: 400
    JSON Error Response: 
        {
            errorMessage: "Bad Request: You submitted invalid input"
        }
    
  6. No veo forma de copiar estas configuraciones y reutilizarlas para diferentes métodos, ¡así que tenemos muchas entradas manuales redundantes y molestas que hacer!

Mis respuestas de integración se ven así:

Manejo de respuesta de error lambda de puerta de enlace aws api

ac360
fuente
3
entonces parece que mi problema fue que el disparador de expresiones regulares nunca funcionó ya que devuelvo un objeto de error de lambda en el método de error, en lugar de solo una cadena. Ej .return context.fail(new Error('bad one'))
MonkeyBonkey
7
@kalisjoshua Recientemente publiqué una publicación bastante detallada sobre el manejo de errores con API Gateway / Lambda: jayway.com/2015/11/07/…
Carl
9
¿Cuál es el equivalente de context.fail para Python Lambda?
routeburn
1
Para python: genere una excepción. Ver docs.aws.amazon.com/lambda/latest/dg/python-exceptions.html
devxoul
1
¿No hay forma de cambiar el código de estado en respuestas sin error? ¿Qué pasa si quisiera enviar "201 Creado" junto con el objeto creado?
Ben Davis
18

Para poder devolver un objeto de error personalizado como JSON, debe pasar por un par de aros.

Primero, debe fallar el Lambda y pasarle un objeto JSON en cadena:

exports.handler = function(event, context) {
    var response = {
        status: 400,
        errors: [
            {
              code:   "123",
              source: "/data/attributes/first-name",
              message:  "Value is too short",
              detail: "First name must contain at least three characters."
            },
            {
              code:   "225",
              source: "/data/attributes/password",
              message: "Passwords must contain a letter, number, and punctuation character.",
              detail: "The password provided is missing a punctuation character."
            },
            {
              code:   "226",
              source: "/data/attributes/password",
              message: "Password and password confirmation do not match."
            }
        ]
    }

    context.fail(JSON.stringify(response));
};

A continuación, configura el mapeo de expresiones regulares para cada uno de los códigos de estado que le gustaría devolver. Usando el objeto que definí anteriormente, configuraría esta expresión regular para 400:

. * "estado": 400. *

Finalmente, configura una plantilla de mapeo para extraer la respuesta JSON de la propiedad errorMessage devuelta por Lambda. La plantilla de mapeo se ve así:

$ input.path ('$. errorMessage')

Escribí un artículo sobre esto que entra en más detalles y explica el flujo de respuesta de Lambda a API Gateway aquí: http://kennbrodhagen.net/2016/03/09/how-to-return-a-custom-error-object -y-código-de-estado-de-api-gateway-with-lambda /

Kennbrodhagen
fuente
@kennbrodhagen ¿Conoce API Gateway y Java Lambdas? Estoy usando una especie de registro exp, y no me funciona. Yo uso. * StatusCode ": 422. *
Perimosh
@Perimosh echa un vistazo a este artículo que explica cómo hacer esto con las excepciones de Java: aws.amazon.com/blogs/compute/…
kennbrodhagen
10

1) Configure su recurso de API Gateway para usar la integración de proxy de Lambda marcando la casilla de verificación etiquetada "Usar integración de proxy de Lambda" en la pantalla "Solicitud de integración" de la definición de recurso de API Gateway. (O defínalo en su configuración de cloudformation / terraform / serverless / etc)

2) Cambie su código lambda de 2 formas

  • Procese la entrada event(argumento de la primera función) de forma adecuada. Ya no es solo la carga útil básica, representa la solicitud HTTP completa, incluidos los encabezados, la cadena de consulta y el cuerpo. Muestra a continuación. El punto clave es que los cuerpos JSON serán cadenas que requieren una JSON.parse(event.body)llamada explícita (no se olvide de try/catcheso). A continuación se muestra un ejemplo.
  • Responda llamando a la devolución de llamada con nulo y luego un objeto de respuesta que proporcione los detalles HTTP statusCode, incluidos body, y headers.
    • bodydebe ser una cuerda, así que haz lo que JSON.stringify(payload)sea ​​necesario
    • statusCode puede ser un número
    • headers es un objeto de nombres de encabezado a valores

Ejemplo de argumento de evento Lambda para la integración de proxy

{
    "resource": "/example-path",
    "path": "/example-path",
    "httpMethod": "POST",
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "CloudFront-Forwarded-Proto": "https",
        "CloudFront-Is-Desktop-Viewer": "true",
        "CloudFront-Is-Mobile-Viewer": "false",
        "CloudFront-Is-SmartTV-Viewer": "false",
        "CloudFront-Is-Tablet-Viewer": "false",
        "CloudFront-Viewer-Country": "US",
        "Content-Type": "application/json",
        "Host": "exampleapiid.execute-api.us-west-2.amazonaws.com",
        "User-Agent": "insomnia/4.0.12",
        "Via": "1.1 9438b4fa578cbce283b48cf092373802.cloudfront.net (CloudFront)",
        "X-Amz-Cf-Id": "oCflC0BzaPQpTF9qVddpN_-v0X57Dnu6oXTbzObgV-uU-PKP5egkFQ==",
        "X-Forwarded-For": "73.217.16.234, 216.137.42.129",
        "X-Forwarded-Port": "443",
        "X-Forwarded-Proto": "https"
    },
    "queryStringParameters": {
        "bar": "BarValue",
        "foo": "FooValue"
    },
    "pathParameters": null,
    "stageVariables": null,
    "requestContext": {
        "accountId": "666",
        "resourceId": "xyz",
        "stage": "dev",
        "requestId": "5944789f-ce00-11e6-b2a2-dfdbdba4a4ee",
        "identity": {
            "cognitoIdentityPoolId": null,
            "accountId": null,
            "cognitoIdentityId": null,
            "caller": null,
            "apiKey": null,
            "sourceIp": "73.217.16.234",
            "accessKey": null,
            "cognitoAuthenticationType": null,
            "cognitoAuthenticationProvider": null,
            "userArn": null,
            "userAgent": "insomnia/4.0.12",
            "user": null
        },
        "resourcePath": "/example-path",
        "httpMethod": "POST",
        "apiId": "exampleapiid"
    },
    "body": "{\n  \"foo\": \"FOO\",\n  \"bar\": \"BAR\",\n  \"baz\": \"BAZ\"\n}\n",
    "isBase64Encoded": false
}

Forma de respuesta de devolución de llamada de muestra

callback(null, {
  statusCode: 409,
  body: JSON.stringify(bodyObject),
  headers: {
    'Content-Type': 'application/json'
  }
})

Notas : creo que los métodos de contexttales como context.succeed()están en desuso. Ya no están documentados aunque todavía parecen funcionar. Creo que codificar la API de devolución de llamada es lo correcto en el futuro.

Peter Lyons
fuente
Esto no funciona. Todavía obtengo 200 estados devueltos con esta salida de respuesta completa. No se puede configurar la API para que realmente devuelva el estado 409
Andy N
7

Quería que un error de Lambda fuera el error 500 correcto, después de investigar mucho, se me ocurrió lo siguiente, que funciona:

En LAMBDA

Para una buena respuesta, vuelvo de la siguiente manera:

exports.handler = (event, context, callback) => {
    // ..

    var someData1 =  {
        data: {
            httpStatusCode: 200,
            details: [
                {
                    prodId: "123",
                    prodName: "Product 1"
                },
                {
                    "more": "213",
                    "moreDetails": "Product 2"
                }
            ]
        }
    };
    return callback(null, someData1);
}

Para una mala respuesta, regresando como se muestra a continuación.

exports.handler = (event, context, callback) => {
    // ..

    var someError1 = {
        error: {
            httpStatusCode: 500,
            details: [
                {
                    code: "ProductNotFound",
                    message: "Product not found in Cart",
                    description: "Product should be present after checkout, but not found in Cart",
                    source: "/data/attributes/product"
                },
                {
                    code: "PasswordConfirmPasswordDoesntMatch",
                    message: "Password and password confirmation do not match.",
                    description: "Password and password confirmation must match for registration to succeed.",
                    source: "/data/attributes/password",
                }
            ]
        }
    };

    return callback(new Error(JSON.stringify(someError1)));
}

En API Gateway

Para obtener un método GET, diga GET of / res1 / service1:

Through Method Response > Add Response, added 3 responses:
- 200
- 300
- 400

Luego,

Through 'Integration Response' > 'Add integration response', create a Regex for 400 errors (client error):

Lambda Error Regex    .*"httpStatusCode":.*4.*

'Body Mapping Templates' > Add mapping template as:  
    Content-Type                 application/json  
    Template text box*           $input.path('$.errorMessage')  


Similarly, create a Regex for 500 errors (server error):

Lambda Error Regex    .*"httpStatusCode":.*5.*

'Body Mapping Templates' > Add mapping template as:  
    Content-Type                 application/json  
    Template text box*           $input.path('$.errorMessage')  

Ahora, publique / res1 / service1, presione la URL publicada, que está conectada a lambda anterior

Si usó el complemento de Chrome del cliente REST avanzado (o Postman), verá los códigos http adecuados, como el error del servidor (500) o 400, en lugar del código de respuesta 200 http para todas las solicitudes que se dieron en "httpStatusCode".

Desde el 'Panel de control' de API, en API Gateway, podemos ver los códigos de estado http como se muestra a continuación:

400 y 500 errores

Manohar Reddy Poreddy
fuente
7

La forma más sencilla de hacerlo es utilizar la integración LAMBDA_PROXY . Con este método, no es necesario que se establezcan transformaciones especiales en la canalización de API Gateway.

Su objeto de devolución tendría que ser similar al fragmento a continuación:

module.exports.lambdaHandler = (event, context, done) => {
    // ...
    let response = {
        statusCode: 200, // or any other HTTP code
        headers: {       // optional
             "any-http-header" : "my custom header value"
        },
        body: JSON.stringify(payload) // data returned by the API Gateway endpoint
    };
    done(null, response); // always return as a success
};

Tiene algunos inconvenientes: debe tener especial cuidado con el manejo de errores y acoplar su función lambda al punto final de API Gateway; Dicho esto, si realmente no lo vas a usar en ningún otro lugar, no es un gran problema.

Ricardo Nolde
fuente
6

Para aquellos que intentaron todo lo que hicieron en esta pregunta y no pudieron hacer que esto funcionara (como yo), revisen el comentario de thedevkit en esta publicación (me salvó el día):

https://forums.aws.amazon.com/thread.jspa?threadID=192918

Reproduciéndolo íntegramente a continuación:

Yo mismo he tenido problemas con esto y creo que los personajes de la nueva línea son los culpables.

foo. * coincidirá con las apariciones de "foo" seguidas de cualquier carácter EXCEPTO la nueva línea. Por lo general, esto se resuelve agregando el indicador '/ s', es decir, "foo. * / S", pero la expresión regular del error Lambda no parece respetar esto.

Como alternativa, puede usar algo como: foo (. | \ N) *

Carlos Ballock
fuente
hallazgo increíble! ¡Me ahorró horas de golpearme la cabeza! Y está lejos de ser obvio.
Mirko Vukušić
Mirko, ¡me alegro de que te haya ayudado!
Carlos Ballock
2

Así es como se recomienda en un blog de AWS Compute si se usa API Gateway. Comprobando si la integración funciona con la invocación directa de Lambda.

var myErrorObj = {
    errorType : "InternalServerError",
    httpStatus : 500,
    requestId : context.awsRequestId,
    message : "An unknown error has occurred. Please try again."
}
callback(JSON.stringify(myErrorObj));

Para las invocaciones directas de Lambda, esta parece ser la mejor solución para el análisis del lado del cliente.

spakmad
fuente
¿y si el ejemplo fuera una llamada lambda a lambda? ¿Es esto todavía lo que devolvería el lambda llamado? y ¿cómo puedo leer ese httpStatus en la llamada lambda.
Rod
1

Estoy usando 0.5 sin servidor. Así es como funciona, para mi caso

s-function.json:

{
  "name": "temp-err-test",
  "description": "Deployed",
  "runtime": "nodejs4.3",
  "handler": "path/to/handler.handler",
  "timeout": 6,
  "memorySize": 1024,
  "endpoints": [
    {
      "path": "test-error-handling",
      "method": "GET",
      "type": "AWS_PROXY",
      "responses": {
        "default": {
          "statusCode": "200"
        }
      }
    }
  ]
}

handler.js:

'use strict';
function serveRequest(event, context, cb) {
  let response = {
    statusCode: '400',
    body: JSON.stringify({ event, context }),
    headers: {
      'Content-Type': 'application/json',
    }
  };
  cb(null, response);
}
module.exports.handler = serveRequest;
Relu Mesaros
fuente
1

Si no desea utilizar un proxy, puede utilizar esta plantilla:

#set($context.responseOverride.status =  $input.path('$.statusCode'))
George Ogden
fuente