Existe uma maneira de alterar os códigos de status http retornados pelo Amazon API Gateway?

95

Por exemplo, se eu quiser retornar um erro 400 específico para parâmetros inválidos ou talvez 201 quando a chamada da função lambda resultou em uma criação.

Eu gostaria de ter códigos de status http diferentes, mas parece que o gateway da API sempre retorna um código de status 200, mesmo se a função lambda estiver retornando um erro.

MonkeyBonkey
fonte
2
portanto, parece que o problema que eu estava tendo era que estava retornando um tipo de erro personalizado - o que faz com que o regex errorMessage não funcione corretamente. Retornar uma string padrão na resposta de falha de lambda fará com que a solução abaixo funcione - retornar seu próprio objeto de erro personalizado, no entanto, não.
MonkeyBonkey
minha solução foi mudar do Serveless versão 0.5 para 1.0. Além disso, estou usando a resposta da documentação do Serveless, especificando o statusCode no objeto de resposta como uma propriedade. Espero que ajude
Relu Mesaros,

Respostas:

79

Atualização por 20-9-2016

A Amazon finalmente tornou isso fácil usando a integração do Lambda Proxy . Isso permite que sua função Lambda retorne códigos e cabeçalhos HTTP adequados:

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

context.succeed(response);

Diga adeus ao mapeamento de solicitação / resposta no API Gateway!

opção 2

Integre um aplicativo Express existente com Lambda / API Gateway usando aws-serverless-express .

Eric Eijkelenboom
fonte
Não consigo integrar, quer dizer, recebo o status 200 e a resposta criada (o erro criado). Estou esquecendo de algo ? Como é o "s-function.json"?
Relu Mesaros,
Para o exemplo mais simples, olhe para o próprio projeto Lambda da AWS chamado microservice-http-endpoint (no console AWS Lambda). Você mencionou "s-function.json", o que parece que você está usando a estrutura Serverless ( serverless.com ). Esta é outra besta totalmente e não deve ser confundida com aws-serverless-express ou Lambda / API Gateway 'bruto'. Minha resposta não descreve como fazer isso funcionar usando a estrutura sem servidor.
Eric Eijkelenboom,
7
Para quem está se perguntando, isso também pode ser feito usando o novo callbackestilo. Apenas faça callback(null, {statusCode: 200, body: 'whatever'}).
Widdershin
1
@Sushil sim, você acabou de retornar o JSON como na variável de resposta acima.
impuro
8
@Sushil Eu resolvi isso em Python com LambdaProxyIntegration e retornandoreturn { "isBase64Encoded": True, "statusCode": 200, "headers": { }, "body": "" }
Jithu R Jacob
74

Esta é a maneira mais rápida de retornar códigos de status HTTP personalizados e um personalizado errorMessage:

No painel do API Gateway, faça o seguinte:

  1. No método do seu recurso , clique na resposta do método
  2. Na tabela Status do HTTP , clique em adicionar resposta e adicione cada Código de status HTTP que deseja usar.
  3. No método do seu recurso , clique na resposta de integração
  4. Adicione uma resposta de integração para cada um dos códigos de status HTTP que você criou anteriormente. Certifique-se de que a passagem de entrada esteja marcada. Use lambda error regex para identificar qual código de status deve ser usado quando você retornar uma mensagem de erro de sua função lambda. Por exemplo:

    // 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. Sua rota de gateway de API deve retornar este:

    HTTP Status Code: 400
    JSON Error Response: 
        {
            errorMessage: "Bad Request: You submitted invalid input"
        }
    
  6. Não vejo como copiar essas configurações e reutilizá-las para métodos diferentes, por isso temos muitas entradas manuais redundantes e irritantes para fazer!

Minhas respostas de integração são assim:

tratamento de resposta de erro lambda do gateway aws api

ac360
fonte
3
portanto, parece que meu problema era que o gatilho regex nunca estava funcionando, pois eu retorno um objeto de erro de lambda no método de falha, em vez de apenas uma string. por exemploreturn context.fail(new Error('bad one'))
MonkeyBonkey
7
@kalisjoshua Publiquei recentemente uma postagem bastante detalhada sobre tratamento de erros com API Gateway / Lambda: jayway.com/2015/11/07/…
Carl
9
Qual é o equivalente de context.fail para Python Lambda?
routeburn de
1
Para python: crie uma exceção. Consulte docs.aws.amazon.com/lambda/latest/dg/python-exceptions.html
devxoul
1
Não há como alterar o código de status em respostas sem erro? E se eu quisesse enviar "201 Created" junto com o objeto criado?
Ben Davis
18

Para ser capaz de retornar um objeto de erro personalizado como JSON, você precisa passar por alguns obstáculos.

Primeiro, você deve reprovar o Lambda e passar um objeto JSON stringificado:

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));
};

Em seguida, você configura o mapeamento de regex para cada um dos códigos de status que deseja retornar. Usando o objeto que defini acima, você configuraria esta regex para 400:

. * "status": 400. *

Finalmente, você configura um modelo de mapeamento para extrair a resposta JSON da propriedade errorMessage retornada pelo Lambda. O modelo de mapeamento tem a seguinte aparência:

$ input.path ('$. errorMessage')

Escrevi um artigo sobre isso que entra em mais detalhes e explica o fluxo de resposta do Lambda para o API Gateway aqui: http://kennbrodhagen.net/2016/03/09/how-to-return-a-custom-error-object -and-status-code-from-api-gateway-with-lambda /

Kennbrodhagen
fonte
@kennbrodhagen você conhece o API Gateway e o Java Lambdas? Estou usando o mesmo reg exp e não está funcionando para mim. Eu uso. * StatusCode ": 422. *
Perimosh
@Perimosh confira este artigo que explica como fazer isso com exceções Java: aws.amazon.com/blogs/compute/…
kennbrodhagen
10

1) Configure seu recurso API Gateway para usar Integração Lambda Proxy marcando a caixa de seleção "Usar integração Lambda Proxy" na tela "Solicitação de Integração" da definição de recurso API Gateway. (Ou defina-o em sua configuração cloudformation / terraform / serverless / etc)

2) Altere seu código lambda de 2 maneiras

  • Processe a entrada event(primeiro argumento da função) de forma adequada. Não é mais apenas a carga útil, mas representa toda a solicitação HTTP, incluindo cabeçalhos, string de consulta e corpo. Amostra abaixo. O ponto principal é que os corpos JSON serão strings que exigemJSON.parse(event.body) chamada (não se esqueça try/catchdisso). O exemplo está abaixo.
  • Responder chamando o callback com nulo, em seguida, um objeto de resposta que fornece os detalhes de HTTP, incluindo statusCode, bodye headers.
    • bodydeve ser uma string, então faça JSON.stringify(payload)conforme necessário
    • statusCode pode ser um número
    • headers é um objeto de nomes de cabeçalho para valores

Exemplo de argumento de evento Lambda para integração 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
}

Amostra de forma de resposta de retorno

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

Notas - Eu acredito que os métodos em contextcomo context.succeed()estão obsoletos. Eles não são mais documentados, embora ainda pareçam funcionar. Acho que a codificação para a API de retorno de chamada é a coisa certa a seguir.

Peter Lyons
fonte
Isso não funciona. Ainda recebo 200 status retornados com toda essa saída de resposta. Não é possível definir a api para realmente retornar o status 409
Andy N
7

Eu queria que um erro do Lambda fosse o próprio erro 500, depois de fazer muita pesquisa, descobri o seguinte, que funciona:

Em LAMBDA

Para uma boa resposta, estou retornando como abaixo:

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 uma resposta ruim, retornando como abaixo

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)));
}

Em API Gateway

Para um GET METHOD, diga GET of / res1 / service1:

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

Então,

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

Agora, publique / res1 / serviço1, acesse o URL publicado, que está conectado ao lambda acima

Utilizado o plugin do cliente REST avançado (ou Postman) do cromo, você verá os códigos http adequados, como erro do servidor (500) ou 400, em vez do código de resposta 200 http para todas as solicitações fornecidas em "httpStatusCode".

No 'Dashboard' da API, no API Gateway, podemos ver os códigos de status http como abaixo:

400 e 500 erros

Manohar Reddy Poreddy
fonte
7

A maneira mais fácil de fazer isso é usar a integração LAMBDA_PROXY . Usando este método, você não precisa de nenhuma transformação especial a ser definida no pipeline do API Gateway.

Seu objeto de retorno deve ser semelhante ao snippet abaixo:

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
};

Ele tem algumas desvantagens: como ter que ser especialmente cuidadoso com o tratamento de erros e acoplar sua função lambda ao terminal do API Gateway; Dito isso, se você realmente não for usá-lo em nenhum outro lugar, não será um grande problema.

Ricardo Nolde
fonte
6

Para aqueles que tentaram de tudo, colocaram esta questão e não conseguiram fazer isso funcionar (como eu), confira o comentário doedevkit neste post (salvou meu dia):

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

Reproduzindo-o inteiramente abaixo:

Eu mesmo tive problemas com isso, e acredito que os personagens de nova linha são os culpados.

foo. * irá corresponder às ocorrências de "foo" seguidas por quaisquer caracteres EXCETO nova linha. Normalmente, isso é resolvido adicionando o sinalizador '/ s', ou seja, "foo. * / S", mas a regex de erro Lambda não parece respeitar isso.

Como alternativa, você pode usar algo como: foo (. | \ N) *

Carlos Ballock
fonte
incrível achado! Isso apenas me salvou de horas batendo minha cabeça! E está longe de ser óbvio.
Mirko Vukušić
Mirko, fico feliz que tenha ajudado você!
Carlos Ballock
2

É assim que é recomendado em um blog de computação da AWS se estiver usando o API Gateway. Verificar se a integração funciona com a invocação direta do Lambda.

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

Para invocações Lambda diretas, esta parece ser a melhor solução de análise no lado do cliente.

Spakmad
fonte
e se o exemplo fosse uma chamada de lambda para lambda. ainda é isso que o lambda chamado retornaria? e como posso ler esse httpStatus no lambda de chamada.
Rod de
1

Estou usando 0,5 sem servidor. É assim que funciona, para o meu 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
fonte
1

Se você não quiser usar um proxy, pode usar este modelo:

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