Existe-t-il un moyen de modifier les codes d'état http renvoyés par Amazon API Gateway?

95

Par exemple, si je veux renvoyer une erreur spécifique 400 pour des paramètres non valides ou peut-être un 201 lorsque l'appel de la fonction lambda a abouti à une création.

J'aimerais avoir différents codes d'état http, mais il semble que la passerelle api renvoie toujours un code d'état 200 même si la fonction lambda renvoie une erreur.

SingeBonkey
la source
2
il semble donc que le problème que j'avais était que je retournais un type d'erreur personnalisé - ce qui rend le regex errorMessage ne fonctionne pas correctement. Le retour d'une chaîne standard dans la réponse d'échec de lambda fera fonctionner la solution ci-dessous - le retour de votre propre objet d'erreur personnalisé ne le sera pas.
MonkeyBonkey
ma solution était de passer de la version 0.5 à 1.0. En outre, j'utilise la réponse de la documentation Serveless, en spécifiant le statusCode dans l'objet de réponse en tant que propriété. J'espère que ça aide
Relu Mesaros

Réponses:

79

Mise à jour du 20-9-2016

Amazon a finalement rendu cela facile en utilisant l' intégration du proxy Lambda . Cela permet à votre fonction Lambda de renvoyer les codes HTTP et les en-têtes appropriés:

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

context.succeed(response);

Dites adieu au mappage des requêtes / réponses dans la passerelle API!

Option 2

Intégrez une application Express existante à Lambda / API Gateway à l'aide de aws-serverless-express .

Eric Eijkelenboom
la source
Je ne peux pas l'intégrer, je veux dire, j'obtiens le statut 200 et la réponse créée (l'erreur créée). Est-ce que je manque quelque chose? À quoi ressemble le "s-function.json"?
Relu Mesaros du
Pour l'exemple le plus simple, regardez le plan d'action Lambda d'AWS appelé microservice-http-endpoint (dans la console AWS Lambda). Vous mentionnez "s-function.json", ce qui ressemble à une utilisation du framework Serverless ( serverless.com ). Ceci est une toute autre bête et ne doit pas être confondu avec aws-serverless-express ou Lambda / API Gateway «brut». Ma réponse ne décrit pas comment faire fonctionner cela en utilisant le framework Serverless.
Eric Eijkelenboom
7
Pour quiconque se demande, cela peut également être réalisé en utilisant le nouveau callbackstyle. Fais juste callback(null, {statusCode: 200, body: 'whatever'}).
Widdershin
1
@Sushil ouais, vous retournez simplement le JSON comme dans la variable de réponse ci-dessus.
démêler le
8
@Sushil J'ai résolu cela en Python avec LambdaProxyIntegration et retourreturn { "isBase64Encoded": True, "statusCode": 200, "headers": { }, "body": "" }
Jithu R Jacob
74

Voici le moyen le plus rapide de renvoyer des codes d'état HTTP personnalisés et un errorMessage :

Dans le tableau de bord API Gateway, procédez comme suit:

  1. Dans la méthode de votre ressource , cliquez sur réponse de la méthode
  2. Dans le tableau État HTTP , cliquez sur ajouter une réponse et ajoutez chaque code d'état HTTP que vous souhaitez utiliser.
  3. Dans la méthode de votre ressource , cliquez sur réponse d'intégration
  4. Ajoutez une réponse d'intégration pour chacun des codes d'état HTTP que vous avez créés précédemment. Assurez-vous que le passthrough d'entrée est coché. Utilisez l' expression régulière d'erreur lambda pour identifier le code d'état à utiliser lorsque vous renvoyez un message d'erreur à partir de votre fonction lambda. Par exemple:

    // 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. Votre route API Gateway doit renvoyer ceci:

    HTTP Status Code: 400
    JSON Error Response: 
        {
            errorMessage: "Bad Request: You submitted invalid input"
        }
    
  6. Je ne vois aucun moyen de copier ces paramètres et de les réutiliser pour différentes méthodes, nous avons donc beaucoup de saisie manuelle redondante ennuyeuse à faire!

Mes réponses d'intégration ressemblent à ceci:

gestion des réponses aux erreurs lambda de la passerelle API aws

ac360
la source
3
il semble donc que mon problème était que le déclencheur regex ne fonctionnait jamais car je retourne un objet d'erreur de lambda dans la méthode fail, plutôt qu'une simple chaîne. par exemplereturn context.fail(new Error('bad one'))
MonkeyBonkey
7
@kalisjoshua J'ai récemment publié un article assez détaillé sur la gestion des erreurs avec API Gateway / Lambda: jayway.com/2015/11/07/…
Carl
9
Quel est l'équivalent de context.fail pour Python Lambda?
routeburn
1
Pour python: lève une exception. Voir docs.aws.amazon.com/lambda/latest/dg/python-exceptions.html
devxoul
1
N'existe-t-il aucun moyen de modifier le code d'état dans les réponses sans erreur? Et si je voulais envoyer "201 Created" avec l'objet créé?
Ben Davis
18

Pour pouvoir renvoyer un objet d'erreur personnalisé au format JSON, vous devez franchir quelques obstacles.

Tout d'abord, vous devez échouer le Lambda et lui passer un objet JSON stringifié:

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

Ensuite, vous configurez le mappage d'expression régulière pour chacun des codes d'état que vous souhaitez renvoyer. En utilisant l'objet que j'ai défini ci-dessus, vous configureriez cette regex pour 400:

. * "statut": 400. *

Enfin, vous configurez un modèle de mappage pour extraire la réponse JSON de la propriété errorMessage renvoyée par Lambda. Le modèle de mappage ressemble à ceci:

$ input.path ('$. errorMessage')

J'ai écrit un article à ce sujet qui entre plus en détail et explique le flux de réponse de Lambda à API Gateway ici: http://kennbrodhagen.net/2016/03/09/how-to-return-a-custom-error-object -et-code-d'état-de-passerelle-api-avec-lambda /

Kennbrodhagen
la source
@kennbrodhagen connaissez-vous API Gateway et Java Lambdas? J'utilise une sorte de la même reg exp, et cela ne fonctionne pas pour moi. J'utilise. * StatusCode ": 422. *
Perimosh
@Perimosh consultez cet article qui explique comment faire cela avec les exceptions Java: aws.amazon.com/blogs/compute/…
kennbrodhagen
10

1) Configurez votre ressource API Gateway pour utiliser l' intégration du proxy Lambda en cochant la case intitulée «Utiliser l'intégration du proxy Lambda» sur l'écran «Demande d'intégration» de la définition de la ressource API Gateway. (Ou définissez-le dans votre configuration cloudformation / terraform / serverless / etc)

2) Changez votre code lambda de 2 façons

  • Traitez eventcorrectement l'entrée (1er argument de fonction). Ce n'est plus seulement la charge utile nue, il représente la requête HTTP entière, y compris les en-têtes, la chaîne de requête et le corps. Exemple ci-dessous. Le point clé est que les corps JSON seront des chaînes nécessitant un JSON.parse(event.body)appel explicite (n'oubliez pastry/catch ). L'exemple est ci-dessous.
  • Répondez en appelant le rappel avec null, puis un objet de réponse qui fournit les détails HTTP statusCode, y compris body, et headers.
    • bodydevrait être une chaîne, alors faites-le JSON.stringify(payload)si nécessaire
    • statusCode peut être un nombre
    • headers est un objet de noms d'en-tête à valeurs

Exemple d'argument d'événement Lambda pour l'intégration du 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
}

Exemple de forme de réponse de rappel

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

Remarques - Je crois que les méthodes contexttelles que context.succeed()sont obsolètes. Ils ne sont plus documentés bien qu'ils semblent toujours fonctionner. Je pense que le codage de l'API de rappel est la bonne chose pour l'avenir.

Peter Lyons
la source
Cela ne fonctionne pas. Je reçois toujours le statut 200 renvoyé avec cette sortie de réponse entière. Impossible de définir l'API pour qu'elle renvoie réellement l'état 409
Andy N
7

Je voulais qu'une erreur de Lambda soit une erreur 500 correcte, après avoir fait beaucoup de recherches, j'ai trouvé ce qui suit, qui fonctionne:

Sur LAMBDA

Pour une bonne réponse, je reviens comme ci-dessous:

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

Pour une mauvaise réponse, retour comme ci-dessous

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

Sur API Gateway

Pour une MÉTHODE GET, dites GET de / res1 / service1:

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

Ensuite,

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

Maintenant, publiez / res1 / service1, appuyez sur l'URL publiée, qui est connectée à lambda ci-dessus

Utilisé le plugin Chrome Advanced REST client (ou Postman), vous verrez les codes http appropriés comme l'erreur de serveur (500) ou 400, au lieu de 200 codes de réponse http pour toutes les requêtes qui ont été données dans "httpStatusCode".

Depuis le `` tableau de bord '' de l'API, dans API Gateway, nous pouvons voir les codes d'état http comme ci-dessous:

Erreurs 400 et 500

Manohar Reddy Poreddy
la source
7

Le moyen le plus simple de procéder consiste à utiliser l'intégration LAMBDA_PROXY . En utilisant cette méthode, vous n'avez besoin d'aucune transformation spéciale pour être définie dans le pipeline API Gateway.

Votre objet de retour devrait être similaire à l'extrait ci-dessous:

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

Cela présente quelques inconvénients: comme devoir être particulièrement prudent dans la gestion des erreurs et coupler votre fonction lambda au point de terminaison API Gateway; Cela dit, si vous n'alliez pas vraiment l'utiliser ailleurs, ce n'est pas vraiment un problème.

Ricardo Nolde
la source
6

Pour ceux qui ont tout essayé sur cette question et n'ont pas pu faire fonctionner cela (comme moi), consultez le commentaire thedevkit sur ce post (a sauvé ma journée):

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

Le reproduire entièrement ci-dessous:

J'ai eu des problèmes avec cela moi-même, et je pense que les personnages de nouvelle ligne sont les coupables.

foo. * correspondra aux occurrences de "foo" suivies de tous les caractères SAUF nouvelle ligne. Typiquement, cela est résolu en ajoutant le drapeau '/ s', c'est-à-dire "foo. * / S", mais l'expression régulière d'erreur Lambda ne semble pas respecter cela.

Comme alternative, vous pouvez utiliser quelque chose comme: foo (. | \ N) *

Carlos Ballock
la source
trouvaille incroyable! Cela m'a sauvé des heures de me cogner la tête! Et c'est loin d'être évident.
Mirko Vukušić
Mirko, je suis content que cela vous ait aidé!
Carlos Ballock
2

C'est ainsi qu'il est recommandé sur un blog AWS Compute si vous utilisez API Gateway. Vérification pour voir si l'intégration fonctionne avec un appel Lambda direct.

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

Pour les appels Lambda directs, cela semble être la meilleure solution d'analyse du côté client.

Spakmad
la source
Et si l'exemple était un appel lambda à lambda. est-ce toujours ce que le lambda appelé reviendrait? et comment puis-je lire ce httpStatus sur le lambda appelant.
Rod
1

J'utilise 0.5 sans serveur. C'est comme ça que ça marche, pour mon cas

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
la source
1

Si vous ne souhaitez pas utiliser de proxy, vous pouvez utiliser ce modèle:

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