Last active
January 19, 2018 11:42
-
-
Save piotrkubisa/2aef965caf3cde747071d02aa3b5dc29 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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