Czy istnieje sposób na zmianę kodów statusu http zwracanych przez Amazon API Gateway?

97

Na przykład, jeśli chcę zwrócić określony błąd 400 dla nieprawidłowych parametrów lub być może 201, gdy wywołanie funkcji lambda spowodowało utworzenie.

Chciałbym mieć różne kody statusu http, ale wygląda na to, że brama API zawsze zwraca kod statusu 200, nawet jeśli funkcja lambda zwraca błąd.

MonkeyBonkey
źródło
2
więc wygląda na to, że problem, który miałem, polegał na tym, że zwracałem niestandardowy typ błędu - co powoduje, że wyrażenie regularne errorMessage nie działa poprawnie. Zwrócenie standardowego ciągu w odpowiedzi fail z lambda spowoduje, że poniższe rozwiązanie zadziała - jednak zwrócenie własnego niestandardowego obiektu błędu nie.
MonkeyBonkey
moim rozwiązaniem było przejście z wersji 0.5 do 1.0 bez serwera. Używam również odpowiedzi z dokumentacji Serveless, określając statusCode w obiekcie odpowiedzi jako właściwość. Mam nadzieję, że to pomoże
Relu Mesaros

Odpowiedzi:

79

Aktualizacja na 20-9-2016

Amazon w końcu ułatwił to dzięki integracji Lambda Proxy . Dzięki temu funkcja Lambda zwraca prawidłowe kody HTTP i nagłówki:

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

context.succeed(response);

Pożegnaj mapowanie żądań / odpowiedzi w bramie interfejsu API!

Opcja 2

Zintegruj istniejącą aplikację Express z Lambda / API Gateway za pomocą aws-serverless-express .

Eric Eijkelenboom
źródło
1
Nie mogę tego zintegrować, czyli mam status 200 i utworzoną odpowiedź (utworzony błąd). Czy coś mi brakuje? Jak wygląda plik „s-function.json”?
Relu Mesaros
7
Każdy, kto się zastanawia, może to również osiągnąć za pomocą nowego callbackstylu. Po prostu zrób callback(null, {statusCode: 200, body: 'whatever'}).
Widdershin
1
@unclemeat Mam te same pytania. Rozgryzłeś to? Jak to zrobić w Pythonie?
Sushil,
1
@Sushil tak, po prostu zwracasz JSON jak w zmiennej odpowiedzi powyżej.
unclemeat
8
@Sushil Rozwiązałem to w Pythonie za pomocą LambdaProxyIntegration i return { "isBase64Encoded": True, "statusCode": 200, "headers": { }, "body": "" }
wróciłem
74

Oto najszybszy sposób na zwrócenie niestandardowych kodów stanu HTTP i niestandardowego errorMessage:

Na pulpicie nawigacyjnym API Gateway wykonaj następujące czynności:

  1. W metodzie dla swojego zasobu kliknij odpowiedź metody
  2. W tabeli Status HTTP kliknij dodaj odpowiedź i dodaj każdy kod statusu HTTP, którego chcesz użyć.
  3. W metodzie dla swojego zasobu kliknij odpowiedź integracji
  4. Dodaj odpowiedź dotyczącą integracji dla każdego utworzonego wcześniej kodu stanu HTTP. Upewnij się, że przekazywanie danych wejściowych jest zaznaczone. Użyj wyrażenia regularnego błędu lambda, aby określić, który kod stanu powinien zostać użyty, gdy zwracasz komunikat o błędzie z funkcji lambda. Na przykład:

    // 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. Twoja trasa API Gateway powinna zwrócić to:

    HTTP Status Code: 400
    JSON Error Response: 
        {
            errorMessage: "Bad Request: You submitted invalid input"
        }
    
  6. Nie widzę sposobu na skopiowanie tych ustawień i ponowne użycie ich do innych metod, więc mamy do zrobienia wiele irytujących, zbędnych ręcznych ustawień!

Moje odpowiedzi dotyczące integracji wyglądają następująco:

aws api brama lambda obsługa odpowiedzi na błąd

ac360
źródło
3
więc wygląda na to, że moim problemem było to, że wyzwalacz wyrażenia regularnego nigdy nie działał, ponieważ zwracam obiekt błędu z lambda w metodzie niepowodzenia, a nie tylko ciąg. np.return context.fail(new Error('bad one'))
MonkeyBonkey
7
@kalisjoshua Niedawno opublikowałem dość szczegółowy post dotyczący obsługi błędów w API Gateway / Lambda: jayway.com/2015/11/07/...
Carl
9
Jaki jest odpowiednik context.fail dla Pythona Lambda?
powrót
1
Python: podnieś wyjątek. Zobacz docs.aws.amazon.com/lambda/latest/dg/python-exceptions.html
devxoul,
1
Czy nie ma sposobu, aby zmienić kod stanu w odpowiedziach innych niż błędy? Co jeśli chciałbym wysłać „201 Created” wraz z utworzonym obiektem?
Ben Davis,
18

Aby móc zwrócić niestandardowy obiekt błędu jako JSON, musisz przejść przez kilka obręczy.

Po pierwsze, musisz zawieść Lambda i przekazać do niej łańcuchowy obiekt JSON:

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

Następnie konfigurujesz mapowanie wyrażeń regularnych dla każdego z kodów stanu, które chcesz zwrócić. Używając obiektu, który zdefiniowałem powyżej, skonfigurowałbyś to wyrażenie regularne dla 400:

. * "status": 400. *

Na koniec konfigurujesz szablon mapowania, aby wyodrębnić odpowiedź JSON z właściwości errorMessage zwróconej przez Lambda. Szablon mapowania wygląda następująco:

$ input.path („$. errorMessage”)

Napisałem artykuł na ten temat, który jest bardziej szczegółowy i wyjaśnia przepływ odpowiedzi z Lambda do API Gateway tutaj: http://kennbrodhagen.net/2016/03/09/how-to-return-a-custom-error-object -and-status-code-from-api-gateway-with-lambda /

kennbrodhagen
źródło
@kennbrodhagen Czy wiesz o API Gateway i Java Lambdas? Używam czegoś w rodzaju tego samego reg expa i to nie działa dla mnie. Używam. * StatusCode ": 422. *
Perimosh
@Perimosh sprawdź ten artykuł, który wyjaśnia, jak to zrobić za pomocą wyjątków Java: aws.amazon.com/blogs/compute/ ...
kennbrodhagen
10

1) Skonfiguruj swój zasób API Gateway do korzystania z integracji Lambda Proxy , zaznaczając pole wyboru oznaczone „Use Lambda Proxy Integration” na ekranie „Integration Request” w definicji zasobów API Gateway. (Lub zdefiniuj to w konfiguracji cloudformation / terraform / serverless / etc)

2) Zmień swój kod lambda na 2 sposoby

  • Przetwarzaj odpowiednio przychodzący event(pierwszy argument funkcji). Nie jest już tylko zwykłym ładunkiem, reprezentuje całe żądanie HTTP, w tym nagłówki, ciąg zapytania i treść. Próbka poniżej. Najważniejsze jest to, że treści JSON będą ciągami wymagającymi jawnego JSON.parse(event.body)wywołania (nie zapomnij try/catcho tym). Przykład poniżej.
  • Odpowiadać wywołując zwrotnego z zerową wówczas obiekcie odpowiedzi, która dostarcza dane HTTP włącznie statusCode, bodyoraz headers.
    • bodypowinien być ciągiem, więc zrób JSON.stringify(payload)w razie potrzeby
    • statusCode może być liczbą
    • headers jest obiektem nazw nagłówków do wartości

Przykładowy argument zdarzenia Lambda dla integracji 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
}

Przykładowy kształt odpowiedzi na wywołanie zwrotne

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

Uwagi - uważam, że metody na contexttakich jak context.succeed()są przestarzałe. Nie są już udokumentowane, chociaż nadal wydają się działać. Myślę, że kodowanie do API zwrotnego jest właściwą rzeczą w przyszłości.

Peter Lyons
źródło
1
To nie działa. Nadal otrzymuję status 200 zwracany z tym całym wyjściem odpowiedzi. Nie można ustawić interfejsu API, aby faktycznie zwracał stan 409
Andy N
7

Chciałem, żeby błąd z Lambdy był poprawny Błąd 500, po przeprowadzeniu wielu badań wymyśliłem poniższe, które działa:

Na LAMBDA

Aby uzyskać dobrą odpowiedź, wracam jak poniżej:

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

W przypadku złej odpowiedzi wracam jak poniżej

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

W API Gateway

Aby uzyskać metodę GET, powiedz GET of / res1 / service1:

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

Następnie,

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

Teraz opublikuj / res1 / service1, kliknij opublikowany adres URL, który jest połączony z powyższą lambdą

Używając zaawansowanej wtyczki Chrome klienta REST (lub Postman), zobaczysz odpowiednie kody http, takie jak błąd serwera (500) lub 400, zamiast kodu odpowiedzi 200 http dla wszystkich żądań, które zostały podane w "httpStatusCode".

W 'Pulpicie nawigacyjnym' API, w API Gateway, możemy zobaczyć kody statusu http, jak poniżej:

400 i 500 błędów

Manohar Reddy Poreddy
źródło
7

Najłatwiej to zrobić, używając integracji LAMBDA_PROXY . Przy użyciu tej metody nie trzeba ustawiać żadnych specjalnych przekształceń w potoku API Gateway.

Twój zwracany obiekt musiałby być podobny do poniższego fragmentu:

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

Ma kilka wad: trzeba szczególnie uważać na obsługę błędów i sprzęganie funkcji lambda z punktem końcowym API Gateway; To powiedziawszy, jeśli tak naprawdę nie zamierzasz go używać nigdzie indziej, nie stanowi to dużego problemu.

Ricardo Nolde
źródło
6

Dla tych, którzy próbowali wszystkiego, co postawili na to pytanie i nie mogli tego zrobić (jak ja), sprawdź komentarz devkit do tego postu (uratował mój dzień):

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

Odtwarzając go w całości poniżej:

Sam miałem z tym problemy i uważam, że winowajcami są znaki nowej linii.

foo. * dopasuje wystąpienia „foo”, po których następują dowolne znaki Z WYJĄTKIEM nowej linii. Zwykle jest to rozwiązywane przez dodanie flagi „/ s”, tj. „Foo. * / S”, ale wyrażenie regularne błędu Lambda wydaje się tego nie uwzględniać.

Alternatywnie możesz użyć czegoś takiego: foo (. | \ N) *

Carlos Ballock
źródło
niesamowite znalezisko! Zaoszczędziło mi to wielu godzin walenia w głowę! I nie jest to oczywiste.
Mirko Vukušić
Mirko, cieszę się, że ci pomogło!
Carlos Ballock
2

W ten sposób jest to zalecane na blogu AWS Compute w przypadku korzystania z bramy API. Sprawdzanie, czy integracja działa z bezpośrednim wywołaniem Lambda.

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

W przypadku bezpośrednich wywołań Lambda wydaje się, że jest to najlepsze rozwiązanie analizujące po stronie klienta.

spakmad
źródło
co by było, gdyby przykład był wywołaniem lambda do lambda. czy to jest wciąż to, co zwróciłaby nazwana lambda? i jak mogę odczytać ten httpStatus na wywołującej lambdzie.
Rod
1

Używam bezserwerowej wersji 0.5. Tak to działa w moim przypadku

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
źródło
1

Jeśli nie chcesz korzystać z serwera proxy, możesz użyć tego szablonu:

#set($context.responseOverride.status =  $input.path('$.statusCode'))
George Ogden
źródło