4

Typically, a AWS Lambda event handler code in Go (using Serverless Framework) is coded as:

package main

import (
  "fmt"
  "context"
  "github.com/aws/aws-lambda-go/lambda"
)

type MyEvent struct {
  Name string `json:"name"`
}

func HandleRequest(ctx context.Context, name MyEvent) (string, error) {
  return fmt.Sprintf("Hello %s!", name.Name ), nil
}

func main() {
  lambda.Start(HandleRequest)
}

The serverless.yml file then contains a section like:

  skeleton-go-get:
    name: skeleton-go-get
    runtime: go1.x
    handler: go-handler  # <- This specifies a file, not a function.
    events:
      - http:
          path: skeleton/go
          method: get

That ^ creates one request handler... but now I want my one Go script / program to contain the event handlers for both HTTP GET and POST requests, and not use one Go program file per serverless function.

Exactly this is possible in languages like Node.js, Ruby, Python, with the serverless.yml specifying which function in the handler file is to be used for which serverless function. For example (for Python functions):

[...]
functions:
  skeleton-python-get:
    name: skeleton-python-get
    handler: python-handler.handle_get  # <- Specifies the HTTP GET handler.
    events:
      - http:
          path: skeleton/python
          method: get
  skeleton-python-post:
    name: skeleton-python-post
    handler: python-handler.handle_post  # <- Specifies the HTTP POST handler.
    events:
      - http:
          path: skeleton/python
          method: post

I cannot get this same trick to work for Go. I tried to include the proper request in main() but to no avail:

func HandleGetRequest(ctx context.Context, name MyEvent) (string, error) {
  return fmt.Sprintf("Hello %s!", name.Name ), nil
}

func HandlePostRequest(ctx context.Context, name MyEvent) (string, error) {
  return fmt.Sprintf("Hello %s!", name.Name ), nil
}

func main() {
  lambda.Start(HandleGetRequest)
  lambda.Start(HandlePostRequest)  // <- Attempt to add another handler.
}

And specifying multiple event handler functions in the serverless.yml file for the Go handlers also doesn't work: the function isn't a valid part of the handler declaration.

  skeleton-go-get:
    name: skeleton-go-get
    runtime: go1.x
    handler: go-handler.HandleGet  # <- Attempt to specify a function.
    events:
      - http:
          path: skeleton/go
          method: get

  skeleton-go-post:
    name: skeleton-go-post
    runtime: go1.x
    handler: go-handler.HandlePost  # <- Attempt to specify a function.
    events:
      - http:
          path: skeleton/go
          method: post

Q: How can I include more than one AWS Lambda event handler in one Go program (using Serverless Framework)?

3
  • What I'm trying to avoid is making one generic (rot) handler for all request types (HTTP GET, HTTP POST), and in that handler differentiate between the request types and relay the logic to the proper functions. But if that is what it requires... Commented Sep 10, 2019 at 14:13
  • 1
    Have you tried calling the first lambda.Start as a goroutine (go lambda.Start(HandleGetRequest))? Per the docs, Start blocks indefinitely, like http.ListenAndServe and similar server functions. Commented Sep 10, 2019 at 14:14
  • Tried, but it doesn't work. The handlers in the serverless.yml apparently seem to be only valid when specifying a file, not a file-and-function. The error when trying to run the event handler is "fork/exec /var/task/go-handler.HandleGet: no such file or directory: PathError", so it looks that a function cannot be specified. Commented Sep 10, 2019 at 14:34

2 Answers 2

4

You can use the same function (and handler) for your get and your post:

skeleton-go:
    name: skeleton-go
    runtime: go1.x
    handler: go-handler
    events:
      - http:
          path: skeleton/go
          method: get
      - http:
          path: skeleton/go
          method: post

Use Go's built-in HTTP router or use a third-party one, such as Gorilla Mux or Chi as shown in the example code below (because that's what I had handy). In essence, you're building a Go HTTP server, but in Lambda. So, follow the details for setting up a Go web server, and take a look at AWS's API Gateway Proxy.

package main

import (
    "context"
    "net/http"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"

    "github.com/go-chi/chi"
    chiproxy "github.com/awslabs/aws-lambda-go-api-proxy/chi"
)

var adapter *chiproxy.ChiLambda

func GetSkeleton(w http.ResponseWriter, r *http.Request) {
    ...
}

func PostSkeletonToMom(w http.ResponseWriter, r *http.Request) {
    ...
}

func init() {
    r := chi.NewRouter()

    r.Get("/skeleton/go", GetSkeleton)
    r.Post("/skeleton/go", PostSkeletonToMom)

    adapter = chiproxy.New(r)
}

func lambdaHandler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    c, err := adapter.ProxyWithContext(ctx, req)

    return c, err
}

func main() {
    lambda.Start(lambdaHandler)
}
Sign up to request clarification or add additional context in comments.

2 Comments

"In essence, you're building a Go HTTP server, but in Lambda." Thanks. I was trying to avoid to make one generic handler for all request methods (HTTP GET, HTTP POST) and to relay the processing logic to the specific event handler functions. That's not necessary in Python, Ruby and JavaScript, as in those languages the proper functions can be specified directly. I guess that's currently not supported (by Serverless Framework, or by Go).
So it is a good idea to use aws-lambda-go-api-proxy in your lambda, even if you have no greedy paths?
0

You shouldn't need to build yourself a Go Server in Lambda as you already have API Gateway served to you from serverless framework...

I used AWS CloudFormation + SAM, and I used HTTP API Gateway (Not REST) but it should function in a similar way...

First of all... you need to make it into 1 Lambda functions for 2 events like this:

skeleton-go-get:
    name: skeleton-go-get
    runtime: go1.x
    handler: go-handler  # <- This specifies a file, not a function.
    events:
      - http:
          path: skeleton/go
          method: get
      - http:
          path: skeleton/go
          method: post

Inside your lambda, you should have:

package main

import ...

func getSkeleton(event events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) {
    // Return APIGateway Response
}

func postSkeleton(event events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) {
    // Return APIGateway Response
}

func handler(_ context.Context, event events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) {
    // Log Events
    eventJson, _ := json.Marshal(event)
    log.Printf("EVENT: %s", string(eventJson))

    switch event.RouteKey {
        case "GET /skeleton/go":
            return getSkeleton(event)
        case "POST /skeleton/go":
            return postSkeleton(event)
        default:
            return events.APIGatewayV2HTTPResponse{
                StatusCode: 400
            }, nil
    }
}

func main() {
    lambda.Start(handler)
}

1 Comment

Thanks, and that’s what I'm trying to avoid: making one generic (root) handler for all request types (HTTP GET, HTTP POST), and in that handler differentiate between the request types and relay the logic to the proper functions. But that does not seem to be possible, alas.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.