AWS Lambda in Go behing API Gateway: custom error message - go

I'm trying to handle an error in a Lambda function written in Go.
The Lambda is triggered by API Gateway.
When I respond with a 200, I get the correct response.
But when I respond with a 500 code, I always receive {"message": "Internal server error"}
Here is part of the code:
func newErrReponse(message string) (events.APIGatewayProxyResponse, error) {
return events.APIGatewayProxyResponse{
Body: message,
StatusCode: 500,
}, errors.New(message)
}
func handleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
return newErrReponse("some error")
}
func main() {
lambda.Start(handleRequest)
}
I was expecting "some error", but I always get the internal server error. I tried JSON in the body but that didn't help.
The integration request is of type LAMBDA_PROXY. That was the default.
How can I control the error response?

The lambda handler can return 2 values. interface{} and error:
https://godoc.org/github.com/aws/aws-lambda-go/lambda
Because I use API Gateway, The interface is of type APIGatewayProxyResponse:
https://godoc.org/github.com/aws/aws-lambda-go/events#APIGatewayProxyResponse
If the Lambda succeeds, the API Gateway will return the values from APIGatewayProxyResponse.
But if the Lambda don't succeeds, then you will get an internal server error.
If you return an error other then nil, then the Lambda failed.
When there is a panic() or os.Exit() then the Lambda also failed.
This means a log.Fatal also fails a Lambda.
Here is more information about errors:
https://docs.aws.amazon.com/lambda/latest/dg/go-programming-model-errors.html
Lambda logs a panic (and other output) in CloudWatch

Related

CognitoIdentityID is empty in Go lambda function with Cognito authentication and API Gateway

I have an AWS lambda function written in Go behind an API Gateway. I'm using Cognito with App integration and OAuth Client credentials grant for authentication. I need to know which App client sent the request inside the lambda function, but all the fields related to Cognito (CognitoIdentityID, CognitoIndetityPoolID, AccountID, etc.) are empty in the context and the request. I can only see the Bearer Authorization in the header of the request. I'm printing the values as follows:
var forwardRequest = func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
println(fmt.Sprintf("Request arrived: Stage: %s, Method: %s, Path: %s",
request.RequestContext.Stage, request.HTTPMethod, request.Path))
lc, _ := lambdacontext.FromContext(ctx)
println(fmt.Sprintf("lc: %+v", lc))
println(fmt.Sprintf("request: %+v", request))
...
}
func main() {
// Make the handler available for Remote Procedure Call by AWS Lambda
lambda.Start(forwardRequest)
}
I'm already using Lambda Proxy integration, as answered in similar questions. I attach a screenshot of the configuration of the API Gateway method. I can't enable "Invoke with caller credentials".
How can I know which client sent the request?.
I found the answer: Cognito's client ID is not in the Cognito* fields but in the Authorizer of the request:
func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
var clientID string
if claimsMap, ok := request.RequestContext.Authorizer["claims"].(map[string]interface{}); ok {
clientID, ok = claimsMap["client_id"].(string)
...
}
...

AWS API Gateway WebSockets [POST]#connections Returning 404 NotFound

I connect a client (or a couple of clients) to the websockets endpoint in API Gateway.
Then, I try to post a message back to the client using these guidelines: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-how-to-call-websocket-api-connections.html
// Send sends a message to a connection ID.
func Send(domain, stage, connectionID, message string) (events.APIGatewayProxyResponse, error) {
session := session.Must(session.NewSession())
endpoint := fmt.Sprintf("https://%s/%s/#connections/%s", domain, stage, connectionID)
apiClient := apigatewaymanagementapi.New(session, aws.NewConfig().WithEndpoint(endpoint))
connectionInput := apigatewaymanagementapi.PostToConnectionInput{
ConnectionId: aws.String(connectionID),
Data: []byte(message),
}
_, err := apiClient.PostToConnection(&connectionInput)
if err != nil {
fmt.Println(err.Error())
return events.APIGatewayProxyResponse{StatusCode: 500}, err
}
return events.APIGatewayProxyResponse{StatusCode: 200}, nil
}
It doesn't matter whether I invoke the Send function locally or a client sends a message and API Gateway invokes my publish Lambda where I loop through the connections and invoke Send for each of them.
The result is always the same.
NotFoundException:
status code: 404, request id: 7bb1546a-c2a7-4e98-92a0-fcc7ae175d7c
Things I've tried:
Escaped #connections and the actual connectionID
Made sure the client connection hasn't timed out
Made sure I have the correct AWS credentials in my environment variables
Made sure my Lambda has permissions to invoke API Gateway
Made sure the endpoint is in the correct format: https://{api-id}.execute-api.{region}.amazonaws.com/{stage}/#connections/{connection_id}
How can I successfully send messages to the clients?
Turns out this line
endpoint := fmt.Sprintf("https://%s/%s/#connections/%s", domain, stage, connectionID)
needs to turn into this
endpoint := fmt.Sprintf("https://%s/%s/", domain, stage)

HTTP client receives status code 200 when server panics with gin-gonic and gin-contrib/gzip

When accessing the gin-gonic server below, the HTTP client should receive the code 500, but receives the code 200.
package main
import (
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
)
func main() {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.Use(gzip.Gzip(gzip.DefaultCompression))
r.POST("/test", func(c *gin.Context) {
panic("test") // Server panic and client should receive code 500.
})
r.Run(":8080")
}
When accessing /test from a HTTP client, the go server log is as below and looks return the code 500.
[GIN] 2020/09/28 - 10:23:14 | 500 | 67.2995ms | ::1 | POST "/test"
2020/09/28 10:23:14 [Recovery] 2020/09/28 - 10:23:14 panic recovered:
test
C:/path/to/myproject/main.go:16 (0x8f193f)
main.func1: panic("test")
But HTTP client receives the code 200.
When I remove r.Use(gzip.Gzip(gzip.DefaultCompression)), the HTTP client receives the code 500.
Why the client receives code 200 with r.Use(gzip.Gzip(gzip.DefaultCompression)), How can I fix this?
I've reproduce your case. Postman got code 200 but the server results 500 instead.
The server will call c.Next() to execute 4 handlers when receive post requests. The sequence is as follow:
gin.Logger
gin.Recovery
gzip.Gzip(gzip.DefaultCompression)
your handler
Here is gin responseWriter writes response header and it will write header only once.
func (w *responseWriter) WriteHeaderNow() {
if !w.Written() {
w.size = 0
w.ResponseWriter.WriteHeader(w.status)
}
}
Both gzip.Gzip(gzip.DefaultCompression) and gin.Recovery has defer func to write response header. Golang's deferred calls are executed in last-in-first-out order. So gzip.Gzip(gzip.DefaultCompression) will write response header to 200, and gin.Recovery won't write reponse header to 500 as expected.
So to solve this problem, you should change the order of handlers and make sure gin.Recovery is the last handler to load.
Adding the recovery middleware last seems to fix this.
package main
import (
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
)
func main() {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Logger())
r.Use(gzip.Gzip(gzip.DefaultCompression))
r.Use(gin.Recovery())
r.POST("/test", func(c *gin.Context) {
panic("test") // Server panic and client should receive code 500.
})
r.Run(":8080")
}

GoLang AWS Lambda Function Missing Body from API Gateway Request

I'm using an AWS Lambda function to handle a request from an AWS API Gateway call. I'm sending a payload on the request, and I can verify in CloudWatch that the payload is being passed from the gateway to the lambda function. However, the body of the request is null inside my Lambda function.
I looked at this question: AWS Lambda Go function not getting request body when called via API GW
I am trying to replicate the answer there by using this library: https://github.com/aws/aws-lambda-go/blob/master/events/apigw.go, but I'm still not able to get the request body.
Here is my Lambda code:
package main
import (
"context"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func handleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
fmt.Println("Body")
fmt.Println(request.Body)
fmt.Printf("Processing request data for request %s.\n", request.RequestContext.RequestID)
fmt.Printf("Processing request data for request %s.\n", request.RequestContext.RequestID)
fmt.Printf("Body size = %d.\n", len(request.Body))
fmt.Println("Headers:")
for key, value := range request.Headers {
fmt.Printf(" %s: %s\n", key, value)
}
return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
}
func main() {
lambda.Start(handleRequest)
}
I'm expecting to see some data after "Body" in Cloudwatch, but there is nothing.
The code in the original question is correct. The second argument to the handleRequest is of the type APIGatewayProxyRequest. In API Gateway, I was sending a normal request, not a proxy request. I redeployed my API Gateway route as a proxy request and got the request body I was expecting. I'm still not really sure whether my original request was failing to send the body, or if the structure of a normal request passed into the handleRequest function is different from that of a proxy request and so the APIGatewayProxyRequest type was unable to parse its body.

How to set HTTP status code on http.ResponseWriter

How do I set the HTTP status code on an http.ResponseWriter (e.g. to 500 or 403)?
I can see that requests normally have a status code of 200 attached to them.
Use http.ResponseWriter.WriteHeader. From the documentation:
WriteHeader sends an HTTP response header with status code. If WriteHeader is not called explicitly, the first call to Write will trigger an implicit WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly used to send error codes.
Example:
func ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Something bad happened!"))
}
Apart from WriteHeader(int) you can use the helper method http.Error, for example:
func yourFuncHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "my own error message", http.StatusForbidden)
// or using the default message error
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
}
http.Error() and http.StatusText() methods are your friends
w.WriteHeader(http.StatusInternalServerError)
w.WriteHeader(http.StatusForbidden)
full list here

Resources