C'è un modo per modificare i codici di stato http restituiti da Amazon API Gateway?


95

Ad esempio, se voglio restituire un errore 400 specifico per parametri non validi o forse un 201 quando la chiamata alla funzione lambda ha prodotto un file create.

Mi piacerebbe avere codici di stato http diversi ma sembra che il gateway API restituisca sempre un codice di stato 200 anche se la funzione lambda restituisce un errore.


2
quindi sembra che il problema che stavo riscontrando fosse che stavo restituendo un tipo di errore personalizzato, il che fa sì che la regex errorMessage non funzioni correttamente. La restituzione di una stringa standard nella risposta di errore da lambda farà funzionare la soluzione seguente, tuttavia la restituzione del proprio oggetto di errore personalizzato non lo farà.
MonkeyBonkey

la mia soluzione era passare dalla versione Serveless 0.5 alla 1.0. Inoltre, sto usando la risposta dalla documentazione di Serveless, specificando lo statusCode nell'oggetto risposta come una proprietà. Spero che aiuti
Relu Mesaros

Risposte:


79

Aggiornamento per 20-9-2016

Amazon ha finalmente reso tutto più semplice utilizzando l' integrazione Lambda Proxy . Ciò consente alla funzione Lambda di restituire i codici HTTP e le intestazioni corretti:

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

context.succeed(response);

Dì addio mappatura richiesta / risposta in API Gateway!

opzione 2

Integra un'app Express esistente con Lambda / API Gateway utilizzando aws-serverless-express .


Non riesco a integrarlo, voglio dire, ottengo lo stato 200 e la risposta creata (l'errore creato). Mi sto perdendo qualcosa ? Come si presenta "s-function.json"?
Relu Mesaros

Per l'esempio più semplice, guarda il progetto Lambda di AWS chiamato microservice-http-endpoint (nella console AWS Lambda). Hai menzionato "s-function.json", che suona come se stessi utilizzando il framework Serverless ( serverless.com ). Questa è un'altra bestia completamente e non deve essere confusa con aws-serverless-express o Lambda / API Gateway "grezzo". La mia risposta non descrive come farlo funzionare utilizzando il framework Serverless.
Eric Eijkelenboom

7
Per chiunque se lo chieda, questo può essere ottenuto anche utilizzando il nuovo callbackstile. Fallo e basta callback(null, {statusCode: 200, body: 'whatever'}).
Widdershin

1
@Sushil sì, restituisci semplicemente il JSON come nella variabile di risposta sopra.
Unclemeat

8
@Sushil Ho risolto questo problema in Python con LambdaProxyIntegration e ritornoreturn { "isBase64Encoded": True, "statusCode": 200, "headers": { }, "body": "" }
Jithu R Jacob il

74

Ecco il modo più veloce per restituire codici di stato HTTP personalizzati e uno personalizzato errorMessage:

Nella dashboard di API Gateway, effettua le seguenti operazioni:

  1. Nel metodo per la tua risorsa , fai clic su risposta del metodo
  2. Nella tabella Stato HTTP , fai clic su aggiungi risposta e aggiungi ogni codice di stato HTTP che desideri utilizzare.
  3. Nel metodo per la tua risorsa , fai clic su risposta di integrazione
  4. Aggiungi una risposta di integrazione per ciascuno dei codici di stato HTTP creati in precedenza. Assicurati che il passthrough di input sia selezionato. Utilizza la regex di errore lambda per identificare il codice di stato da utilizzare quando restituisci un messaggio di errore dalla funzione lambda. Per esempio:

    // 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. Il tuo percorso API Gateway dovrebbe restituire questo:

    HTTP Status Code: 400
    JSON Error Response: 
        {
            errorMessage: "Bad Request: You submitted invalid input"
        }
    
  6. Non vedo alcun modo per copiare queste impostazioni e riutilizzarle per metodi diversi, quindi abbiamo molte fastidiose immissioni manuali ridondanti da fare!

Le mie risposte di integrazione hanno questo aspetto:

gestione della risposta agli errori lambda del gateway api aws


3
quindi sembra che il mio problema fosse che il trigger di regex non funzionava mai poiché restituisco un oggetto errore da lambda nel metodo fail, piuttosto che solo una stringa. ad esempioreturn context.fail(new Error('bad one'))
MonkeyBonkey

7
@kalisjoshua Ho recentemente pubblicato un post abbastanza dettagliato sulla gestione degli errori con API Gateway / Lambda: jayway.com/2015/11/07/…
Carl

9
Qual è l'equivalente di context.fail per Python Lambda?
Routeburn

1
Per python: solleva un'eccezione. Vedi docs.aws.amazon.com/lambda/latest/dg/python-exceptions.html
devxoul

1
Non è possibile modificare il codice di stato nelle risposte non di errore? E se volessi inviare "201 Created" insieme all'oggetto creato?
Ben Davis

18

Per poter restituire un oggetto di errore personalizzato come JSON devi saltare un paio di cerchi.

Innanzitutto, devi fallire il Lambda e passargli un oggetto JSON con stringhe:

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

Successivamente, imposti la mappatura regex per ciascuno dei codici di stato che desideri restituire. Usando l'oggetto che ho definito sopra dovresti impostare questa regex per 400:

. * "status": 400. *

Infine, imposti un modello di mappatura per estrarre la risposta JSON dalla proprietà errorMessage restituita da Lambda. Il modello di mappatura ha questo aspetto:

$ input.path ('$. messaggio di errore')

Ho scritto un articolo su questo argomento che va più in dettaglio e spiega il flusso di risposta da Lambda a API Gateway qui: http://kennbrodhagen.net/2016/03/09/how-to-return-a-custom-error-object -and-status-code-from-api-gateway-with-lambda /


@kennbrodhagen conosci API Gateway e Java Lambdas? Sto usando una sorta della stessa esperienza di registrazione e non funziona per me. Io uso. * StatusCode ": 422. *
Perimosh

@Perimosh controlla questo articolo che spiega come farlo con le eccezioni Java: aws.amazon.com/blogs/compute/…
kennbrodhagen

10

1) Configura la tua risorsa API Gateway per utilizzare Lambda Proxy Integration selezionando la casella di controllo "Usa integrazione Lambda Proxy" nella schermata "Integration Request" della definizione della risorsa API Gateway. (Oppure definiscilo nel tuo cloudformation / terraform / serverless / etc config)

2) Cambia il tuo codice lambda in 2 modi

  • Elabora in modo eventappropriato l'argomento in entrata (1 ° argomento della funzione) Non è più solo il carico utile nudo, rappresenta l'intera richiesta HTTP comprese le intestazioni, la stringa di query e il corpo. Esempio di seguito. Il punto chiave è che i corpi JSON saranno stringhe che richiedono una JSON.parse(event.body)chiamata esplicita (non dimenticaretry/catch ). L'esempio è sotto.
  • Rispondono chiamando il callback con null quindi un oggetto risposta che fornisce i dettagli HTTP compresi statusCode, bodye headers.
    • bodydovrebbe essere una stringa, quindi fai JSON.stringify(payload)come necessario
    • statusCode può essere un numero
    • headers è un oggetto di nomi di intestazione a valori

Esempio di argomento dell'evento Lambda per l'integrazione 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
}

Esempio di forma di risposta alla richiamata

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

Note - Credo che i metodi su contextcome context.succeed()siano deprecati. Non sono più documentati anche se sembrano ancora funzionare. Penso che la codifica per l'API di callback sia la cosa corretta in futuro.


Questo non funziona. Ottengo ancora 200 stato restituito con l'intero output di risposta. Impossibile impostare l'API per restituire effettivamente lo stato 409
Andy N

7

Volevo che un errore da Lambda fosse corretto 500, dopo aver fatto molte ricerche, ho trovato il seguente, che funziona:

Su LAMBDA

Per una buona risposta, torno come di seguito:

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

Per una cattiva risposta, tornando come sotto

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

Su API Gateway

Per un METODO GET, dire GET of / res1 / service1:

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

Poi,

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

Ora, pubblica / ris1 / servizio1, premi l'URL pubblicato, che è connesso a lambda sopra

Utilizzato il plug-in di Chrome del client REST avanzato (o Postman), vedrai i codici http corretti come l'errore del server (500) o 400, invece di 200 codici di risposta http per tutte le richieste in cui sono state fornite in "httpStatusCode".

Dalla "Dashboard" dell'API, in API Gateway, possiamo vedere i codici di stato http come di seguito:

Errori 400 e 500


7

Il modo più semplice per farlo è utilizzare l'integrazione LAMBDA_PROXY . Utilizzando questo metodo, non è necessario impostare trasformazioni speciali nella pipeline di API Gateway.

Il tuo oggetto di restituzione dovrebbe essere simile allo snippet di seguito:

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

Ha alcuni svantaggi: come dover prestare particolare attenzione alla gestione degli errori e accoppiare la funzione lambda all'endpoint di API Gateway; detto questo, se non lo userai da nessun'altra parte, non è un gran problema.


6

Per coloro che hanno provato di tutto, hanno posto questa domanda e non sono riusciti a farlo funzionare (come me), controlla il commento di thedevkit su questo post (mi ha salvato la giornata):

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

Riproducendolo interamente di seguito:

Ho avuto problemi con questo me stesso e credo che i personaggi di nuova riga siano i colpevoli.

foo. * corrisponderà alle occorrenze di "foo" seguito da qualsiasi carattere TRANNE la nuova riga. Tipicamente questo viene risolto aggiungendo il flag '/ s', cioè "foo. * / S", ma la regex dell'errore Lambda non sembra rispettarlo.

In alternativa puoi usare qualcosa come: foo (. | \ N) *


scoperta incredibile! Mi ha solo risparmiato ore di sbattere la testa! Ed è tutt'altro che ovvio.
Mirko Vukušić

Mirko, sono contento che ti abbia aiutato!
Carlos Ballock

2

Ecco come è consigliato su un blog AWS Compute se si utilizza API Gateway. Controllo per vedere se l'integrazione funziona con la chiamata Lambda diretta.

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

Per le chiamate Lambda dirette, questa sembra essere la soluzione migliore per l'analisi lato client.


e se l'esempio fosse una chiamata da lambda a lambda. è ancora questo ciò che restituirebbe la lambda chiamata? e come posso leggere quel httpStatus sul lambda chiamante.
Rod

1

Sto usando 0,5 serverless. Ecco come funziona, per il mio 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;

1

Se non desideri utilizzare un proxy, puoi utilizzare questo modello:

#set($context.responseOverride.status =  $input.path('$.statusCode'))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.