feat(aws): support lambda (#334)

This commit is contained in:
Bo-Yi Wu
2018-01-23 16:34:34 +08:00
committed by GitHub
parent b0260af17b
commit d7ce3c077c
43 changed files with 2795 additions and 2 deletions

52
vendor/github.com/aws/aws-lambda-go/lambda/entry.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
package lambda
import (
"fmt"
"log"
"net"
"net/rpc"
"os"
)
// Start takes a handler, and talks to and internal Lambda endpoint to pass Invoke requests to the handler. If a
// handler does not match one of the supported types, the lambda package will respond to new invokes served by in
// internal endpoint with an appropriate error message. 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 implement "context.Context".
// * handler may return between 0 and two arguments.
// * If there are two return values, the second argument must implement "error".
// * If there is one return value it must implement "error".
//
// 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{}) {
port := os.Getenv("_LAMBDA_SERVER_PORT")
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%s", port))
if err != nil {
log.Fatal(err)
}
wrappedHandler := newHandler(handler)
function := new(Function)
function.handler = wrappedHandler
err = rpc.Register(function)
if err != nil {
log.Fatal("failed to register handler function")
}
rpc.Accept(lis)
log.Fatal("accept should not have returned")
}

83
vendor/github.com/aws/aws-lambda-go/lambda/function.go generated vendored Normal file
View File

@@ -0,0 +1,83 @@
// 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 lambdaHandler
}
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,
}
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,
}
}

123
vendor/github.com/aws/aws-lambda-go/lambda/handler.go generated vendored Normal file
View File

@@ -0,0 +1,123 @@
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
package lambda
import (
"context"
"encoding/json"
"fmt"
"reflect"
)
// 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 the base lambda handler, which will do basic payload unmarshaling before defering to handlerSymbol.
// If handlerSymbol is not a valid handler, the returned function will be a handler that just reports the validation error.
func newHandler(handlerSymbol interface{}) lambdaHandler {
if handlerSymbol == nil {
return errorHandler(fmt.Errorf("handler is nil"))
}
handler := reflect.ValueOf(handlerSymbol)
handlerType := reflect.TypeOf(handlerSymbol)
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 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
}
}

View File

@@ -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
vendor/github.com/aws/aws-lambda-go/lambda/panic.go generated vendored Normal file
View File

@@ -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,
}
}