Skip to content

Instantly share code, notes, and snippets.

@piotrkubisa
Last active January 19, 2018 11:42
Show Gist options
  • Save piotrkubisa/2aef965caf3cde747071d02aa3b5dc29 to your computer and use it in GitHub Desktop.
Save piotrkubisa/2aef965caf3cde747071d02aa3b5dc29 to your computer and use it in GitHub Desktop.
package server
import (
"encoding/base64"
"encoding/json"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"strings"
"sync"
"github.com/aws/aws-lambda-go/events"
)
// ServerlessHandler starts unstarted HTTP server to mimic AWS API Gateway proxy
// and handle REST API request
func ServerlessHandler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
// FIXME: Replace router with your own http.Handler (i.e. chi routing)
var router http.Handler
listener := newLocalListener()
srv := StartServerlessServer(listener, nil)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
http.Serve(listener, router)
}()
return srv.Handle(request)
}
// ServerlessServer defines parameters for handling requests coming from
// Amazon API Gateway. The zero value for Server is not a valid configuration,
// use StartServerlessServer instead.
//
// Implementation is based on github.com/eawsy/aws-lambda-go-net
type ServerlessServer struct {
pt string
ts map[string]bool
}
// Listen returns a Go net.Listener listening on a system-chosen port on the
// local loopback interface.
//
// Implementation is based on github.com/eawsy/aws-lambda-go-net
func newLocalListener() net.Listener {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic(err)
}
return l
}
// StartServerlessServer returns an initialized server to handle requests
// from Amazon API Gateway.
// The given media types slice may be nil, if Amazon API Gateway Binary support
// is not enabled. Otherwise, it should be an array of supported media types as
// configured in Amazon API Gateway.
//
// Implementation is based on github.com/eawsy/aws-lambda-go-net
func StartServerlessServer(ln net.Listener, ts []string) *ServerlessServer {
s := &ServerlessServer{"http://" + ln.Addr().String(), make(map[string]bool)}
for _, t := range ts {
s.ts[t] = true
}
return s
}
// Implementation is based on github.com/eawsy/aws-lambda-go-net
func newClient() *http.Client {
return &http.Client{
CheckRedirect: func(*http.Request, []*http.Request) error {
return http.ErrUseLastResponse
},
}
}
// Handle responds to an AWS Lambda proxy function invocation via Amazon API
// Gateway.
// It transforms the Amazon API Gateway Proxy event to a standard HTTP request
// suitable for the Go net/http package. Then, it submits the data to the
// network listener so that it can be consumed by HTTP handler. Finally, it
// waits for the network listener to return response from handler and transmits
// it back to Amazon API Gateway.
//
// Implementation is based on github.com/eawsy/aws-lambda-go-net
func (s *ServerlessServer) Handle(gwreq events.APIGatewayProxyRequest) (gwres events.APIGatewayProxyResponse, dummy error) {
gwres = events.APIGatewayProxyResponse{StatusCode: http.StatusInternalServerError}
u, err := url.Parse(gwreq.Path)
if err != nil {
log.Println(err)
return
}
q := u.Query()
for k, v := range gwreq.QueryStringParameters {
q.Set(k, v)
}
u.RawQuery = q.Encode()
dec := gwreq.Body
if gwreq.IsBase64Encoded {
data, err := base64.StdEncoding.DecodeString(dec)
if err != nil {
log.Println(err)
return
}
dec = string(data)
}
req, err := http.NewRequest(gwreq.HTTPMethod, s.pt+u.String(), strings.NewReader(dec))
if err != nil {
log.Println(err)
return
}
gwreq.Body = "... truncated"
for k, v := range gwreq.Headers {
req.Header.Set(k, v)
}
if len(req.Header.Get("X-Forwarded-For")) == 0 {
req.Header.Set("X-Forwarded-For", gwreq.RequestContext.Identity.SourceIP)
}
hbody, err := json.Marshal(gwreq)
if err != nil {
log.Println(err)
return
}
req.Header.Set("X-ApiGatewayProxy-Event", string(hbody))
// TODO: Commented out, because context (ctx) has not been provided as argument
// hctx, err := json.Marshal(ctx)
// if err != nil {
// log.Println(err)
// return
// }
// req.Header.Set("X-ApiGatewayProxy-Context", string(hctx))
req.Host = gwreq.Headers["Host"]
res, err := newClient().Do(req)
if err != nil {
log.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Println(err)
return
}
ct := res.Header.Get("Content-Type")
if ct == "" {
ct = http.DetectContentType(body)
res.Header.Set("Content-Type", ct)
}
if _, ok := s.ts[ct]; ok {
gwres.Body = base64.StdEncoding.EncodeToString(body)
gwres.IsBase64Encoded = true
} else {
gwres.Body = string(body)
}
gwres.Headers = make(map[string]string)
for k := range res.Header {
gwres.Headers[k] = res.Header.Get(k)
}
gwres.StatusCode = res.StatusCode
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment