Skip to content
/ agw Public

Route AWS APIGateway to lambda with standard router such as mux

Notifications You must be signed in to change notification settings

davyzhang/agw

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Route AWS APIGateway Requests with ease

AGW transforms AWS lambda event message to the standard http.Request which can make it easy to work with the existing http routers and chaining libraries. With AWS's native support for golang wrapper, shims like nodejs or python is no longer needed.

In short, the usage is

//your standard http handler
func testhandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Add("test", "test header")
    w.(*agw.LPResponse).WriteBody(map[string]string{
        "test":    "test body",
        "funcArn": agw.LambdaContext.InvokedFunctionArn, //can access context as global variable
        "event":   string(agw.RawMessage),               //can access RawMessage as global variable
    }, false)
}

func main() {
    //use any exsiting router supporting the standard http.Handler
    //like 	"github.com/gorilla/mux"
    mux := mux.NewRouter()
    mux.HandleFunc("/test1/hello", testhandler)
    //lambda is from official sdk "https://github.com/aws/aws-lambda-go"
    lambda.Start(agw.Handler(mux))
}

The Full Picture

To use it in the real project it might need some more setups

  1. AWS APIGateway must be configured with lambda proxy mode, typically {/proxy+} here's the doc from aws
  2. Using any http router such as lightning fast Bone or popular and feature rich Gorilla Mux and even with chaining libraries like Alice to write your middlewares

A complex example

You can deploy this code to aws lambda and link it to apigateway to see how it works in the test console of aws apigateway.

func handler1(w http.ResponseWriter, r *http.Request) {
    p1 := bone.GetValue(r, "var")
    bd := string(r.Context().Value(agw.ContextKeyBody).([]byte))
    w.(*agw.LPResponse).WriteBody(map[string]string{
        "agent": r.UserAgent(),
        "var":   p1,
        "bd":    bd,
    }, false)
}

func main() {
    mux := bone.New()
    cors := alice.New(agw.Logging, agw.EnableCORS, agw.ParseBodyBytes)
    mux.Post("/test1/:var", cors.ThenFunc(handler1))

    lambda.Start(func() agw.GatewayHandler {
        return func(ctx context.Context, event json.RawMessage) (interface{}, error) {
            //might be useful to store ctx and event as global variable here
            agp := agw.NewAPIGateParser(event)
            lctx, _ := lambdacontext.FromContext(ctx)
            //deal with the different ctx and event,such as connecting to different db endpoint
            //or setting up global variables
            log.Printf("init here with method %s, req ctx: %+v", agp.Method(), lctx)
            return agw.Process(agp, mux), nil
        }
    }())
} 

Support both standard http server and the lambda environment

It is a common scenario that debugging programs with local servers. However, aws lambda and apigateway have limited this possibility. We must upload the code and wait for the logs coming out in the cloudwatch, which is slow and inconvenient. This library provides a little wrapper func to write back data that can help to identify the real type of http.ResponseWriter and invoke corresponding write function.

func handlerTest(w http.ResponseWriter, r *http.Request) { //you can pass this handler to a standard local http server
    //your code...
    agw.WriteResponse(w, ret, false) //corresponding Write function will be invoked
})

Firstly, build the standard http router

func buildMux() http.Handler {
	mux := bone.New()
	cors := alice.New(agw.Logging, agw.EnableCORS)
    mux.Get("/test", cors.ThenFunc(handlerTest))
    mux.Options("/*", cors.ThenFunc(handlerDummy)) //handle option requests to support POST/PATCH.. requests
}

Typically, there are 2 entry points, one for local or standard server which looks like this:

func DevHTTPEntry() {
	srv := &http.Server{
		Handler:      buildMux(),
		Addr:         "localhost:9090",
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}
	log.Fatal(srv.ListenAndServe())
}

and there's another one for lambda:

func LambdaHTTPEntry() {
	lambda.Start(func() agw.GatewayHandler {
		return func(ctx context.Context, event json.RawMessage) (interface{}, error) {
			agp := agw.NewAPIGateParser(event)
			return agw.Process(agp, buildMux()), nil
		}
	}())
}

then switch them according to the environment variable

func HTTPEntry() {
	plat, ok := os.LookupEnv("APP_PLATFORM")
	if !ok || plat != "Lambda" {
		DevHTTPEntry()
	} else {
		LambdaHTTPEntry()
	}
}

Notes

  • The ResponseWriter.Write([]byte) (int, error) is not going to work as normal http response due to the way how lambda and aws apigateway work
  • You need to type assert ResponseWriter as (*agw.LPResponse) and use WriteBody(out, false|true) to set the return body
  • Information about aws's base64 support is here
 func MyHandler(w http.ResponseWriter, r *http.Request) {
    //your logic ....
    w.(*agw.LPResponse).WriteBody(out, false)//false|true indicates whether the body is encoded with base64 or not
}

License

BSD licensed. See the LICENSE file for details.

About

Route AWS APIGateway to lambda with standard router such as mux

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages