瀏覽代碼

[WIP] AWS lambda support

DarthSim 6 年之前
父節點
當前提交
361af6398a

+ 13 - 0
Gopkg.lock

@@ -25,6 +25,18 @@
   revision = "5d049714c4a64225c3c79a7cf7d02f7fb5b96338"
   version = "1.0.0"
 
+[[projects]]
+  digest = "1:4105793d064c026895f82f9fb76520146c012fc10980a6496d8663491c179faf"
+  name = "github.com/aws/aws-lambda-go"
+  packages = [
+    "lambda",
+    "lambda/messages",
+    "lambdacontext",
+  ]
+  pruneopts = "NUT"
+  revision = "9e3676ee8ca83aee500682f382e10b9d03660093"
+  version = "v1.8.0"
+
 [[projects]]
   digest = "1:5494cf2a25198713bb2ada9824abccc5610230f6d35c79deec16258a9a92203b"
   name = "github.com/aws/aws-sdk-go"
@@ -537,6 +549,7 @@
   analyzer-version = 1
   input-imports = [
     "cloud.google.com/go/storage",
+    "github.com/aws/aws-lambda-go/lambda",
     "github.com/aws/aws-sdk-go/aws",
     "github.com/aws/aws-sdk-go/aws/session",
     "github.com/aws/aws-sdk-go/service/s3",

+ 61 - 0
lambda.go

@@ -0,0 +1,61 @@
+package main
+
+import (
+	"context"
+	"encoding/base64"
+	"encoding/json"
+	"strings"
+
+	"github.com/aws/aws-lambda-go/lambda"
+)
+
+type lambdaHandler struct{}
+
+type labdaResponse struct {
+	IsBase64Encoded bool              `json:"'isBase64Encoded'"`
+	StatusCode      int               `json:"statusCode"`
+	Headers         map[string]string `json:"headers"`
+	Body            string            `json:"body"`
+}
+
+func (s lambdaHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) {
+	ctx = timerWithSince(ctx)
+
+	ctx, err := parsePath(ctx, strings.TrimSpace(string(payload)), &processingHeaders{})
+	if err != nil {
+		return []byte{}, err
+	}
+
+	ctx, downloadcancel, err := downloadImage(ctx)
+	defer downloadcancel()
+	if err != nil {
+		return []byte{}, err
+	}
+
+	checkTimeout(ctx)
+
+	data, err := processImage(ctx)
+	if err != nil {
+		return []byte{}, err
+	}
+
+	po := getProcessingOptions(ctx)
+
+	resp := labdaResponse{
+		IsBase64Encoded: true,
+		StatusCode:      200,
+		Headers:         map[string]string{"Content-Type": mimes[po.Format]},
+		Body:            base64.StdEncoding.EncodeToString(data),
+	}
+
+	jsonData, err := json.Marshal(resp)
+	if err != nil {
+		return []byte{}, err
+	}
+
+	return jsonData, nil
+}
+
+func startLambdaServer() {
+	lambda.StartHandler(new(lambdaHandler))
+}

+ 10 - 6
main.go

@@ -19,13 +19,17 @@ func main() {
 		}()
 	}
 
-	s := startServer()
+	if len(os.Getenv("_LAMBDA_SERVER_PORT")) == 0 {
+		s := startServer()
 
-	stop := make(chan os.Signal, 1)
-	signal.Notify(stop, os.Interrupt, os.Kill)
+		stop := make(chan os.Signal, 1)
+		signal.Notify(stop, os.Interrupt, os.Kill)
 
-	<-stop
+		<-stop
 
-	shutdownServer(s)
-	shutdownVips()
+		shutdownServer(s)
+		shutdownVips()
+	} else {
+		startLambdaServer()
+	}
 }

+ 14 - 9
processing_options.go

@@ -833,8 +833,7 @@ func parsePathBasic(parts []string, headers *processingHeaders) (string, *proces
 	return url, po, nil
 }
 
-func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
-	path := r.URL.Path
+func parsePath(ctx context.Context, path string, headers *processingHeaders) (context.Context, error) {
 	parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
 
 	if len(parts) < 3 {
@@ -847,13 +846,6 @@ func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
 		}
 	}
 
-	headers := &processingHeaders{
-		Accept:        r.Header.Get("Accept"),
-		Width:         r.Header.Get("Width"),
-		ViewportWidth: r.Header.Get("Viewport-Width"),
-		DPR:           r.Header.Get("DPR"),
-	}
-
 	var imageURL string
 	var po *processingOptions
 	var err error
@@ -874,6 +866,19 @@ func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
 	return ctx, nil
 }
 
+func parseRequest(ctx context.Context, r *http.Request) (context.Context, error) {
+	path := r.URL.Path
+
+	headers := processingHeaders{
+		Accept:        r.Header.Get("Accept"),
+		Width:         r.Header.Get("Width"),
+		ViewportWidth: r.Header.Get("Viewport-Width"),
+		DPR:           r.Header.Get("DPR"),
+	}
+
+	return parsePath(ctx, path, &headers)
+}
+
 func getImageURL(ctx context.Context) string {
 	return ctx.Value(imageURLCtxKey).(string)
 }

+ 46 - 46
processing_options_test.go

@@ -23,7 +23,7 @@ func (s *ProcessingOptionsTestSuite) getRequest(url string) *http.Request {
 func (s *ProcessingOptionsTestSuite) TestParseBase64URL() {
 	imageURL := "http://images.dev/lorem/ipsum.jpg?param=value"
 	req := s.getRequest(fmt.Sprintf("http://example.com/unsafe/size:100:100/%s.png", base64.RawURLEncoding.EncodeToString([]byte(imageURL))))
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 	assert.Equal(s.T(), imageURL, getImageURL(ctx))
@@ -33,7 +33,7 @@ func (s *ProcessingOptionsTestSuite) TestParseBase64URL() {
 func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithoutExtension() {
 	imageURL := "http://images.dev/lorem/ipsum.jpg?param=value"
 	req := s.getRequest(fmt.Sprintf("http://example.com/unsafe/size:100:100/%s", base64.RawURLEncoding.EncodeToString([]byte(imageURL))))
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 	assert.Equal(s.T(), imageURL, getImageURL(ctx))
@@ -45,7 +45,7 @@ func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithBase() {
 
 	imageURL := "lorem/ipsum.jpg?param=value"
 	req := s.getRequest(fmt.Sprintf("http://example.com/unsafe/size:100:100/%s.png", base64.RawURLEncoding.EncodeToString([]byte(imageURL))))
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 	assert.Equal(s.T(), fmt.Sprintf("%s%s", conf.BaseURL, imageURL), getImageURL(ctx))
@@ -55,7 +55,7 @@ func (s *ProcessingOptionsTestSuite) TestParseBase64URLWithBase() {
 func (s *ProcessingOptionsTestSuite) TestParseBase64URLInvalid() {
 	imageURL := "lorem/ipsum.jpg?param=value"
 	req := s.getRequest(fmt.Sprintf("http://example.com/unsafe/size:100:100/%s.png", base64.RawURLEncoding.EncodeToString([]byte(imageURL))))
-	_, err := parsePath(context.Background(), req)
+	_, err := parseRequest(context.Background(), req)
 
 	require.Error(s.T(), err)
 	assert.Equal(s.T(), errInvalidImageURL.Error(), err.Error())
@@ -64,7 +64,7 @@ func (s *ProcessingOptionsTestSuite) TestParseBase64URLInvalid() {
 func (s *ProcessingOptionsTestSuite) TestParsePlainURL() {
 	imageURL := "http://images.dev/lorem/ipsum.jpg"
 	req := s.getRequest(fmt.Sprintf("http://example.com/unsafe/size:100:100/plain/%s@png", imageURL))
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 	assert.Equal(s.T(), imageURL, getImageURL(ctx))
@@ -75,7 +75,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePlainURLWithoutExtension() {
 	imageURL := "http://images.dev/lorem/ipsum.jpg"
 	req := s.getRequest(fmt.Sprintf("http://example.com/unsafe/size:100:100/plain/%s", imageURL))
 
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 	assert.Equal(s.T(), imageURL, getImageURL(ctx))
@@ -84,7 +84,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePlainURLWithoutExtension() {
 func (s *ProcessingOptionsTestSuite) TestParsePlainURLEscaped() {
 	imageURL := "http://images.dev/lorem/ipsum.jpg?param=value"
 	req := s.getRequest(fmt.Sprintf("http://example.com/unsafe/size:100:100/plain/%s@png", url.PathEscape(imageURL)))
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 	assert.Equal(s.T(), imageURL, getImageURL(ctx))
@@ -96,7 +96,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePlainURLWithBase() {
 
 	imageURL := "lorem/ipsum.jpg"
 	req := s.getRequest(fmt.Sprintf("http://example.com/unsafe/size:100:100/plain/%s@png", imageURL))
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 	assert.Equal(s.T(), fmt.Sprintf("%s%s", conf.BaseURL, imageURL), getImageURL(ctx))
@@ -108,7 +108,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePlainURLEscapedWithBase() {
 
 	imageURL := "lorem/ipsum.jpg?param=value"
 	req := s.getRequest(fmt.Sprintf("http://example.com/unsafe/size:100:100/plain/%s@png", url.PathEscape(imageURL)))
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 	assert.Equal(s.T(), fmt.Sprintf("%s%s", conf.BaseURL, imageURL), getImageURL(ctx))
@@ -118,7 +118,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePlainURLEscapedWithBase() {
 func (s *ProcessingOptionsTestSuite) TestParsePlainURLInvalid() {
 	imageURL := "lorem/ipsum.jpg?param=value"
 	req := s.getRequest(fmt.Sprintf("http://example.com/unsafe/size:100:100/plain/%s@png", imageURL))
-	_, err := parsePath(context.Background(), req)
+	_, err := parseRequest(context.Background(), req)
 
 	require.Error(s.T(), err)
 	assert.Equal(s.T(), errInvalidImageURL.Error(), err.Error())
@@ -127,7 +127,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePlainURLInvalid() {
 func (s *ProcessingOptionsTestSuite) TestParsePlainURLEscapedInvalid() {
 	imageURL := "lorem/ipsum.jpg?param=value"
 	req := s.getRequest(fmt.Sprintf("http://example.com/unsafe/size:100:100/plain/%s@png", url.PathEscape(imageURL)))
-	_, err := parsePath(context.Background(), req)
+	_, err := parseRequest(context.Background(), req)
 
 	require.Error(s.T(), err)
 	assert.Equal(s.T(), errInvalidImageURL.Error(), err.Error())
@@ -135,7 +135,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePlainURLEscapedInvalid() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathBasic() {
 	req := s.getRequest("http://example.com/unsafe/fill/100/200/noea/1/plain/http://images.dev/lorem/ipsum.jpg@png")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -150,7 +150,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathBasic() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedFormat() {
 	req := s.getRequest("http://example.com/unsafe/format:webp/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -160,7 +160,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedFormat() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedResize() {
 	req := s.getRequest("http://example.com/unsafe/resize:fill:100:200:1/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -173,7 +173,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedResize() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedResizingType() {
 	req := s.getRequest("http://example.com/unsafe/resizing_type:fill/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -183,7 +183,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedResizingType() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedSize() {
 	req := s.getRequest("http://example.com/unsafe/size:100:200:1/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -195,7 +195,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedSize() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedWidth() {
 	req := s.getRequest("http://example.com/unsafe/width:100/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -205,7 +205,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedWidth() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedHeight() {
 	req := s.getRequest("http://example.com/unsafe/height:100/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -215,7 +215,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedHeight() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedEnlarge() {
 	req := s.getRequest("http://example.com/unsafe/enlarge:1/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -225,7 +225,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedEnlarge() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedGravity() {
 	req := s.getRequest("http://example.com/unsafe/gravity:soea/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -235,7 +235,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedGravity() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedGravityFocuspoint() {
 	req := s.getRequest("http://example.com/unsafe/gravity:fp:0.5:0.75/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -247,7 +247,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedGravityFocuspoint() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedQuality() {
 	req := s.getRequest("http://example.com/unsafe/quality:55/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -257,7 +257,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedQuality() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedBackground() {
 	req := s.getRequest("http://example.com/unsafe/background:128:129:130/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -270,7 +270,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedBackground() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedBackgroundHex() {
 	req := s.getRequest("http://example.com/unsafe/background:ffddee/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -283,7 +283,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedBackgroundHex() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedBackgroundDisable() {
 	req := s.getRequest("http://example.com/unsafe/background:fff/background:/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -293,7 +293,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedBackgroundDisable() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedBlur() {
 	req := s.getRequest("http://example.com/unsafe/blur:0.2/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -303,7 +303,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedBlur() {
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedSharpen() {
 	req := s.getRequest("http://example.com/unsafe/sharpen:0.2/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -312,7 +312,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedSharpen() {
 }
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedDpr() {
 	req := s.getRequest("http://example.com/unsafe/dpr:2/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -321,7 +321,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedDpr() {
 }
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedWatermark() {
 	req := s.getRequest("http://example.com/unsafe/watermark:0.5:soea:10:20:0.6/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -344,7 +344,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedPreset() {
 	}
 
 	req := s.getRequest("http://example.com/unsafe/preset:test1:test2/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -362,7 +362,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathPresetDefault() {
 	}
 
 	req := s.getRequest("http://example.com/unsafe/quality:70/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -383,14 +383,14 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedPresetLoopDetection()
 	}
 
 	req := s.getRequest("http://example.com/unsafe/preset:test1:test2:test1/plain/http://images.dev/lorem/ipsum.jpg")
-	_, err := parsePath(context.Background(), req)
+	_, err := parseRequest(context.Background(), req)
 
 	require.Error(s.T(), err)
 }
 
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedCachebuster() {
 	req := s.getRequest("http://example.com/unsafe/cachebuster:123/plain/http://images.dev/lorem/ipsum.jpg")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -403,7 +403,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWebpDetection() {
 
 	req := s.getRequest("http://example.com/unsafe/plain/http://images.dev/lorem/ipsum.jpg")
 	req.Header.Set("Accept", "image/webp")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -416,7 +416,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWebpDetectionRedefine() {
 
 	req := s.getRequest("http://example.com/unsafe/plain/http://images.dev/lorem/ipsum.jpg@png")
 	req.Header.Set("Accept", "image/webp")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -429,7 +429,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWebpEnforce() {
 
 	req := s.getRequest("http://example.com/unsafe/plain/http://images.dev/lorem/ipsum.jpg@png")
 	req.Header.Set("Accept", "image/webp")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -442,7 +442,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeader() {
 
 	req := s.getRequest("http://example.com/unsafe/plain/http://images.dev/lorem/ipsum.jpg@png")
 	req.Header.Set("Width", "100")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -453,7 +453,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeader() {
 func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeaderDisabled() {
 	req := s.getRequest("http://example.com/unsafe/plain/http://images.dev/lorem/ipsum.jpg@png")
 	req.Header.Set("Width", "100")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -466,7 +466,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathWidthHeaderRedefine() {
 
 	req := s.getRequest("http://example.com/unsafe/width:150/plain/http://images.dev/lorem/ipsum.jpg@png")
 	req.Header.Set("Width", "100")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -479,7 +479,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathViewportWidthHeader() {
 
 	req := s.getRequest("http://example.com/unsafe/plain/http://images.dev/lorem/ipsum.jpg@png")
 	req.Header.Set("Viewport-Width", "100")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -490,7 +490,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathViewportWidthHeader() {
 func (s *ProcessingOptionsTestSuite) TestParsePathViewportWidthHeaderDisabled() {
 	req := s.getRequest("http://example.com/unsafe/plain/http://images.dev/lorem/ipsum.jpg@png")
 	req.Header.Set("Viewport-Width", "100")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -503,7 +503,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathViewportWidthHeaderRedefine()
 
 	req := s.getRequest("http://example.com/unsafe/width:150/plain/http://images.dev/lorem/ipsum.jpg@png")
 	req.Header.Set("Viewport-Width", "100")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -516,7 +516,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathDprHeader() {
 
 	req := s.getRequest("http://example.com/unsafe/plain/http://images.dev/lorem/ipsum.jpg@png")
 	req.Header.Set("DPR", "2")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -527,7 +527,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathDprHeader() {
 func (s *ProcessingOptionsTestSuite) TestParsePathDprHeaderDisabled() {
 	req := s.getRequest("http://example.com/unsafe/plain/http://images.dev/lorem/ipsum.jpg@png")
 	req.Header.Set("DPR", "2")
-	ctx, err := parsePath(context.Background(), req)
+	ctx, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 
@@ -541,7 +541,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathSigned() {
 	conf.AllowInsecure = false
 
 	req := s.getRequest("http://example.com/HcvNognEV1bW6f8zRqxNYuOkV0IUf1xloRb57CzbT4g/width:150/plain/http://images.dev/lorem/ipsum.jpg@png")
-	_, err := parsePath(context.Background(), req)
+	_, err := parseRequest(context.Background(), req)
 
 	require.Nil(s.T(), err)
 }
@@ -552,7 +552,7 @@ func (s *ProcessingOptionsTestSuite) TestParsePathSignedInvalid() {
 	conf.AllowInsecure = false
 
 	req := s.getRequest("http://example.com/unsafe/width:150/plain/http://images.dev/lorem/ipsum.jpg@png")
-	_, err := parsePath(context.Background(), req)
+	_, err := parseRequest(context.Background(), req)
 
 	require.Error(s.T(), err)
 	assert.Equal(s.T(), errInvalidSignature.Error(), err.Error())

+ 1 - 1
server.go

@@ -248,7 +248,7 @@ func (h *httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
 	ctx, timeoutCancel := startTimer(ctx, time.Duration(conf.WriteTimeout)*time.Second)
 	defer timeoutCancel()
 
-	ctx, err := parsePath(ctx, r)
+	ctx, err := parseRequest(ctx, r)
 	if err != nil {
 		panic(err)
 	}

+ 5 - 4
timer.go

@@ -8,11 +8,12 @@ import (
 
 var timerSinceCtxKey = ctxKey("timerSince")
 
+func timerWithSince(ctx context.Context) context.Context {
+	return context.WithValue(ctx, timerSinceCtxKey, time.Now())
+}
+
 func startTimer(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
-	return context.WithTimeout(
-		context.WithValue(ctx, timerSinceCtxKey, time.Now()),
-		d,
-	)
+	return context.WithTimeout(timerWithSince(ctx), d)
 }
 
 func getTimerSince(ctx context.Context) time.Duration {

+ 203 - 0
vendor/github.com/aws/aws-lambda-go/LICENSE

@@ -0,0 +1,203 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+

+ 15 - 0
vendor/github.com/aws/aws-lambda-go/LICENSE-LAMBDACODE

@@ -0,0 +1,15 @@
+MIT No Attribution
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this
+software and associated documentation files (the "Software"), to deal in the Software
+without restriction, including without limitation the rights to use, copy, modify,
+merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+

+ 7 - 0
vendor/github.com/aws/aws-lambda-go/LICENSE-SUMMARY

@@ -0,0 +1,7 @@
+Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+Lambda functions are made available under a modified MIT license.
+See LICENSE-LAMBDACODE for details.
+
+The remainder of the project is made available under the terms of the
+Apache License, version 2.0. See LICENSE for details.

+ 64 - 0
vendor/github.com/aws/aws-lambda-go/lambda/entry.go

@@ -0,0 +1,64 @@
+// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+package lambda
+
+import (
+	"log"
+	"net"
+	"net/rpc"
+	"os"
+)
+
+// Start takes a handler and talks to an internal Lambda endpoint to pass requests to the handler. If the
+// handler does not match one of the supported types an appropriate error message will be returned to the caller.
+// Start blocks, and does not return after being called.
+//
+// Rules:
+//
+// 	* handler must be a function
+// 	* handler may take between 0 and two arguments.
+// 	* if there are two arguments, the first argument must satisfy the "context.Context" interface.
+// 	* handler may return between 0 and two arguments.
+// 	* if there are two return values, the second argument must be an error.
+// 	* if there is one return value it must be an error.
+//
+// Valid function signatures:
+//
+// 	func ()
+// 	func () error
+// 	func (TIn) error
+// 	func () (TOut, error)
+// 	func (TIn) (TOut, error)
+// 	func (context.Context) error
+// 	func (context.Context, TIn) error
+// 	func (context.Context) (TOut, error)
+// 	func (context.Context, TIn) (TOut, error)
+//
+// Where "TIn" and "TOut" are types compatible with the "encoding/json" standard library.
+// See https://golang.org/pkg/encoding/json/#Unmarshal for how deserialization behaves
+func Start(handler interface{}) {
+	wrappedHandler := NewHandler(handler)
+	StartHandler(wrappedHandler)
+}
+
+// StartHandler takes in a Handler wrapper interface which can be implemented either by a
+// custom function or a struct.
+//
+// Handler implementation requires a single "Invoke()" function:
+//
+//  func Invoke(context.Context, []byte) ([]byte, error)
+func StartHandler(handler Handler) {
+	port := os.Getenv("_LAMBDA_SERVER_PORT")
+	lis, err := net.Listen("tcp", "localhost:"+port)
+	if err != nil {
+		log.Fatal(err)
+	}
+	function := new(Function)
+	function.handler = handler
+	err = rpc.Register(function)
+	if err != nil {
+		log.Fatal("failed to register handler function")
+	}
+	rpc.Accept(lis)
+	log.Fatal("accept should not have returned")
+}

+ 87 - 0
vendor/github.com/aws/aws-lambda-go/lambda/function.go

@@ -0,0 +1,87 @@
+// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+package lambda
+
+import (
+	"context"
+	"encoding/json"
+	"reflect"
+	"time"
+
+	"github.com/aws/aws-lambda-go/lambda/messages"
+	"github.com/aws/aws-lambda-go/lambdacontext"
+)
+
+type Function struct {
+	handler Handler
+}
+
+func (fn *Function) Ping(req *messages.PingRequest, response *messages.PingResponse) error {
+	*response = messages.PingResponse{}
+	return nil
+}
+
+func (fn *Function) Invoke(req *messages.InvokeRequest, response *messages.InvokeResponse) error {
+	defer func() {
+		if err := recover(); err != nil {
+			panicInfo := getPanicInfo(err)
+			response.Error = &messages.InvokeResponse_Error{
+				Message:    panicInfo.Message,
+				Type:       getErrorType(err),
+				StackTrace: panicInfo.StackTrace,
+				ShouldExit: true,
+			}
+		}
+	}()
+
+	deadline := time.Unix(req.Deadline.Seconds, req.Deadline.Nanos).UTC()
+	invokeContext, cancel := context.WithDeadline(context.Background(), deadline)
+	defer cancel()
+
+	lc := &lambdacontext.LambdaContext{
+		AwsRequestID:       req.RequestId,
+		InvokedFunctionArn: req.InvokedFunctionArn,
+		Identity: lambdacontext.CognitoIdentity{
+			CognitoIdentityID:     req.CognitoIdentityId,
+			CognitoIdentityPoolID: req.CognitoIdentityPoolId,
+		},
+	}
+	if len(req.ClientContext) > 0 {
+		if err := json.Unmarshal(req.ClientContext, &lc.ClientContext); err != nil {
+			response.Error = lambdaErrorResponse(err)
+			return nil
+		}
+	}
+	invokeContext = lambdacontext.NewContext(invokeContext, lc)
+
+	invokeContext = context.WithValue(invokeContext, "x-amzn-trace-id", req.XAmznTraceId)
+
+	payload, err := fn.handler.Invoke(invokeContext, req.Payload)
+	if err != nil {
+		response.Error = lambdaErrorResponse(err)
+		return nil
+	}
+	response.Payload = payload
+	return nil
+}
+
+func getErrorType(err interface{}) string {
+	errorType := reflect.TypeOf(err)
+	if errorType.Kind() == reflect.Ptr {
+		return errorType.Elem().Name()
+	}
+	return errorType.Name()
+}
+
+func lambdaErrorResponse(invokeError error) *messages.InvokeResponse_Error {
+	var errorName string
+	if errorType := reflect.TypeOf(invokeError); errorType.Kind() == reflect.Ptr {
+		errorName = errorType.Elem().Name()
+	} else {
+		errorName = errorType.Name()
+	}
+	return &messages.InvokeResponse_Error{
+		Message: invokeError.Error(),
+		Type:    errorName,
+	}
+}

+ 130 - 0
vendor/github.com/aws/aws-lambda-go/lambda/handler.go

@@ -0,0 +1,130 @@
+// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+package lambda
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"reflect"
+)
+
+type Handler interface {
+	Invoke(ctx context.Context, payload []byte) ([]byte, error)
+}
+
+// lambdaHandler is the generic function type
+type lambdaHandler func(context.Context, []byte) (interface{}, error)
+
+// Invoke calls the handler, and serializes the response.
+// If the underlying handler returned an error, or an error occurs during serialization, error is returned.
+func (handler lambdaHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) {
+	response, err := handler(ctx, payload)
+	if err != nil {
+		return nil, err
+	}
+
+	responseBytes, err := json.Marshal(response)
+	if err != nil {
+		return nil, err
+	}
+
+	return responseBytes, nil
+}
+
+func errorHandler(e error) lambdaHandler {
+	return func(ctx context.Context, event []byte) (interface{}, error) {
+		return nil, e
+	}
+}
+
+func validateArguments(handler reflect.Type) (bool, error) {
+	handlerTakesContext := false
+	if handler.NumIn() > 2 {
+		return false, fmt.Errorf("handlers may not take more than two arguments, but handler takes %d", handler.NumIn())
+	} else if handler.NumIn() > 0 {
+		contextType := reflect.TypeOf((*context.Context)(nil)).Elem()
+		argumentType := handler.In(0)
+		handlerTakesContext = argumentType.Implements(contextType)
+		if handler.NumIn() > 1 && !handlerTakesContext {
+			return false, fmt.Errorf("handler takes two arguments, but the first is not Context. got %s", argumentType.Kind())
+		}
+	}
+
+	return handlerTakesContext, nil
+}
+
+func validateReturns(handler reflect.Type) error {
+	errorType := reflect.TypeOf((*error)(nil)).Elem()
+	if handler.NumOut() > 2 {
+		return fmt.Errorf("handler may not return more than two values")
+	} else if handler.NumOut() > 1 {
+		if !handler.Out(1).Implements(errorType) {
+			return fmt.Errorf("handler returns two values, but the second does not implement error")
+		}
+	} else if handler.NumOut() == 1 {
+		if !handler.Out(0).Implements(errorType) {
+			return fmt.Errorf("handler returns a single value, but it does not implement error")
+		}
+	}
+	return nil
+}
+
+// NewHandler creates a base lambda handler from the given handler function. The
+// returned Handler performs JSON deserialization and deserialization, and
+// delegates to the input handler function.  The handler function parameter must
+// satisfy the rules documented by Start.  If handlerFunc is not a valid
+// handler, the returned Handler simply reports the validation error.
+func NewHandler(handlerFunc interface{}) Handler {
+	if handlerFunc == nil {
+		return errorHandler(fmt.Errorf("handler is nil"))
+	}
+	handler := reflect.ValueOf(handlerFunc)
+	handlerType := reflect.TypeOf(handlerFunc)
+	if handlerType.Kind() != reflect.Func {
+		return errorHandler(fmt.Errorf("handler kind %s is not %s", handlerType.Kind(), reflect.Func))
+	}
+
+	takesContext, err := validateArguments(handlerType)
+	if err != nil {
+		return errorHandler(err)
+	}
+
+	if err := validateReturns(handlerType); err != nil {
+		return errorHandler(err)
+	}
+
+	return lambdaHandler(func(ctx context.Context, payload []byte) (interface{}, error) {
+		// construct arguments
+		var args []reflect.Value
+		if takesContext {
+			args = append(args, reflect.ValueOf(ctx))
+		}
+		if (handlerType.NumIn() == 1 && !takesContext) || handlerType.NumIn() == 2 {
+			eventType := handlerType.In(handlerType.NumIn() - 1)
+			event := reflect.New(eventType)
+
+			if err := json.Unmarshal(payload, event.Interface()); err != nil {
+				return nil, err
+			}
+
+			args = append(args, event.Elem())
+		}
+
+		response := handler.Call(args)
+
+		// convert return values into (interface{}, error)
+		var err error
+		if len(response) > 0 {
+			if errVal, ok := response[len(response)-1].Interface().(error); ok {
+				err = errVal
+			}
+		}
+		var val interface{}
+		if len(response) > 1 {
+			val = response[0].Interface()
+		}
+
+		return val, err
+	})
+}

+ 43 - 0
vendor/github.com/aws/aws-lambda-go/lambda/messages/messages.go

@@ -0,0 +1,43 @@
+// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+package messages
+
+type PingRequest struct {
+}
+
+type PingResponse struct {
+}
+
+type InvokeRequest_Timestamp struct {
+	Seconds int64
+	Nanos   int64
+}
+
+type InvokeRequest struct {
+	Payload               []byte
+	RequestId             string
+	XAmznTraceId          string
+	Deadline              InvokeRequest_Timestamp
+	InvokedFunctionArn    string
+	CognitoIdentityId     string
+	CognitoIdentityPoolId string
+	ClientContext         []byte
+}
+
+type InvokeResponse struct {
+	Payload []byte
+	Error   *InvokeResponse_Error
+}
+
+type InvokeResponse_Error struct {
+	Message    string
+	Type       string
+	StackTrace []*InvokeResponse_Error_StackFrame
+	ShouldExit bool
+}
+
+type InvokeResponse_Error_StackFrame struct {
+	Path  string `json:"path"`
+	Line  int32  `json:"line"`
+	Label string `json:"label"`
+}

+ 99 - 0
vendor/github.com/aws/aws-lambda-go/lambda/panic.go

@@ -0,0 +1,99 @@
+// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+package lambda
+
+import (
+	"fmt"
+	"runtime"
+	"strings"
+
+	"github.com/aws/aws-lambda-go/lambda/messages"
+)
+
+type panicInfo struct {
+	Message    string                                      // Value passed to panic call, converted to string
+	StackTrace []*messages.InvokeResponse_Error_StackFrame // Stack trace of the panic
+}
+
+func getPanicInfo(value interface{}) panicInfo {
+	message := getPanicMessage(value)
+	stack := getPanicStack()
+
+	return panicInfo{Message: message, StackTrace: stack}
+}
+
+func getPanicMessage(value interface{}) string {
+	return fmt.Sprintf("%v", value)
+}
+
+var defaultErrorFrameCount = 32
+
+func getPanicStack() []*messages.InvokeResponse_Error_StackFrame {
+	s := make([]uintptr, defaultErrorFrameCount)
+	const framesToHide = 3 // this (getPanicStack) -> getPanicInfo -> handler defer func
+	n := runtime.Callers(framesToHide, s)
+	if n == 0 {
+		return make([]*messages.InvokeResponse_Error_StackFrame, 0)
+	}
+
+	s = s[:n]
+
+	return convertStack(s)
+}
+
+func convertStack(s []uintptr) []*messages.InvokeResponse_Error_StackFrame {
+	var converted []*messages.InvokeResponse_Error_StackFrame
+	frames := runtime.CallersFrames(s)
+
+	for {
+		frame, more := frames.Next()
+
+		formattedFrame := formatFrame(frame)
+		converted = append(converted, formattedFrame)
+
+		if !more {
+			break
+		}
+	}
+	return converted
+}
+
+func formatFrame(inputFrame runtime.Frame) *messages.InvokeResponse_Error_StackFrame {
+	path := inputFrame.File
+	line := int32(inputFrame.Line)
+	label := inputFrame.Function
+
+	// Strip GOPATH from path by counting the number of seperators in label & path
+	//
+	// For example given this:
+	//     GOPATH = /home/user
+	//     path   = /home/user/src/pkg/sub/file.go
+	//     label  = pkg/sub.Type.Method
+	//
+	// We want to set:
+	//     path  = pkg/sub/file.go
+	//     label = Type.Method
+
+	i := len(path)
+	for n, g := 0, strings.Count(label, "/")+2; n < g; n++ {
+		i = strings.LastIndex(path[:i], "/")
+		if i == -1 {
+			// Something went wrong and path has less seperators than we expected
+			// Abort and leave i as -1 to counteract the +1 below
+			break
+		}
+	}
+
+	path = path[i+1:] // Trim the initial /
+
+	// Strip the path from the function name as it's already in the path
+	label = label[strings.LastIndex(label, "/")+1:]
+	// Likewise strip the package name
+	label = label[strings.Index(label, ".")+1:]
+
+	return &messages.InvokeResponse_Error_StackFrame{
+		Path:  path,
+		Line:  line,
+		Label: label,
+	}
+}

+ 89 - 0
vendor/github.com/aws/aws-lambda-go/lambdacontext/context.go

@@ -0,0 +1,89 @@
+// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+//
+// Helpers for accessing context information from an Invoke request. Context information
+// is stored in a https://golang.org/pkg/context/#Context. The functions FromContext and NewContext
+// are used to retrieving and inserting an isntance of LambdaContext.
+
+package lambdacontext
+
+import (
+	"context"
+	"os"
+	"strconv"
+)
+
+// LogGroupName is the name of the log group that contains the log streams of the current Lambda Function
+var LogGroupName string
+
+// LogStreamName name of the log stream that the current Lambda Function's logs will be sent to
+var LogStreamName string
+
+// FunctionName the name of the current Lambda Function
+var FunctionName string
+
+// MemoryLimitInMB is the configured memory limit for the current instance of the Lambda Function
+var MemoryLimitInMB int
+
+// FunctionVersion is the published version of the current instance of the Lambda Function
+var FunctionVersion string
+
+func init() {
+	LogGroupName = os.Getenv("AWS_LAMBDA_LOG_GROUP_NAME")
+	LogStreamName = os.Getenv("AWS_LAMBDA_LOG_STREAM_NAME")
+	FunctionName = os.Getenv("AWS_LAMBDA_FUNCTION_NAME")
+	if limit, err := strconv.Atoi(os.Getenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")); err != nil {
+		MemoryLimitInMB = 0
+	} else {
+		MemoryLimitInMB = limit
+	}
+	FunctionVersion = os.Getenv("AWS_LAMBDA_FUNCTION_VERSION")
+}
+
+// ClientApplication is metadata about the calling application.
+type ClientApplication struct {
+	InstallationID string `json:"installation_id"`
+	AppTitle       string `json:"app_title"`
+	AppVersionCode string `json:"app_version_code"`
+	AppPackageName string `json:"app_package_name"`
+}
+
+// ClientContext is information about the client application passed by the calling application.
+type ClientContext struct {
+	Client ClientApplication
+	Env    map[string]string `json:"env"`
+	Custom map[string]string `json:"custom"`
+}
+
+// CognitoIdentity is the cognito identity used by the calling application.
+type CognitoIdentity struct {
+	CognitoIdentityID     string
+	CognitoIdentityPoolID string
+}
+
+// LambdaContext is the set of metadata that is passed for every Invoke.
+type LambdaContext struct {
+	AwsRequestID       string
+	InvokedFunctionArn string
+	Identity           CognitoIdentity
+	ClientContext      ClientContext
+}
+
+// An unexported type to be used as the key for types in this package.
+// This prevents collisions with keys defined in other packages.
+type key struct{}
+
+// The key for a LambdaContext in Contexts.
+// Users of this package must use lambdacontext.NewContext and lambdacontext.FromContext
+// instead of using this key directly.
+var contextKey = &key{}
+
+// NewContext returns a new Context that carries value lc.
+func NewContext(parent context.Context, lc *LambdaContext) context.Context {
+	return context.WithValue(parent, contextKey, lc)
+}
+
+// FromContext returns the LambdaContext value stored in ctx, if any.
+func FromContext(ctx context.Context) (*LambdaContext, bool) {
+	lc, ok := ctx.Value(contextKey).(*LambdaContext)
+	return lc, ok
+}