Personal blog

AWS Lambda is a serverless computing service that allows code to run without presetting or managing a server. We simply write the code, upload it to the Lambda as a ZIP file or container image, and trigger it with any Web or mobile application or AWS service or Saas application.

Today, read aws- Lambda-GO to see how lambda can run the code of go’s function.

Entry in the lambda/entry. Go

func Start(handler interface{}) {
	StartWithContext(context.Background(), handler)
}
Copy the code

The Start method has the following rules, and handler is the function we define:

  • Handler must be a function
  • Handler supports 0 to 2 parameters
  • If handler takes two arguments, the first must becontext.Context
  • Hander returns 0-2 values
  • If handler returns two values, the second must be Error
  • If handler returns only one value, this must be error

For example

func handleRequest(ctx context.Context, event events.SQSEvent) (string, error){... }Copy the code

Now let’s look at the StartWithContext method, which calls StartHandlerWithContext

func StartWithContext(ctx context.Context, handler interface{}) {
	StartHandlerWithContext(ctx, NewHandler(handler))
}
Copy the code

Let’s take a look at the NewHandler method, which is a little bit long, but simply do some checking and create a basic lambda function with the handler, and then call this lambda function to actually call the function code that we uploaded

func NewHandler(handlerFunc interface{}) Handler {
    // There are some validations in front
	if handlerFunc == nil {
		return errorHandler(fmt.Errorf("handler is nil"))
	}
	handler := reflect.ValueOf(handlerFunc)
	handlerType := reflect.TypeOf(handlerFunc)
	ifhandlerType.Kind() ! = reflect.Func {return errorHandler(fmt.Errorf("handler kind %s is not %s", handlerType.Kind(), reflect.Func))
	}

    // validateArguments check the handler's input arguments
    // If there are more than 2 input arguments, or if there are 2 input arguments but the first one is not context. context, return false, error
    // Return false, nil when the input argument is 1, but not of type context. context
	takesContext, err := validateArguments(handlerType)
	iferr ! =nil {
		return errorHandler(err)
	}

    // Verify the returned value
	iferr := validateReturns(handlerType); err ! =nil {
		return errorHandler(err)
	}

    // Return a basic lambda function
	return lambdaHandler(func(ctx context.Context, payload []byte) (interface{}, error) {

		trace := handlertrace.FromContext(ctx)

		// construct arguments
		var args []reflect.Value
		if takesContext {
            // The first input is context. context, which is added to args
			args = append(args, reflect.ValueOf(ctx))
		}
        // If handler has only one argument and is not context.Context or two arguments, parse the event in the argument and put it in args
		if (handlerType.NumIn() == 1 && !takesContext) || handlerType.NumIn() == 2 {
			eventType := handlerType.In(handlerType.NumIn() - 1)
			event := reflect.New(eventType)

			iferr := json.Unmarshal(payload, event.Interface()); err ! =nil {
				return nil, err
			}
			if nil! = trace.RequestEvent { trace.RequestEvent(ctx, event.Elem().Interface()) } args =append(args, event.Elem())
		}

        // Pass the arguments to handler and call it
		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()

			if nil! = trace.ResponseEvent { trace.ResponseEvent(ctx, val) } }return val, err
	})
}
Copy the code

Look at StartHandlerWithContext, which calls the startRuntimeAPILoop method

func StartHandlerWithContext(ctx context.Context, handler Handler) {
	var keys []string
	for _, start := range startFunctions {
        // strart.env is a string "AWS_LAMBDA_RUNTIME_API"
		config := os.Getenv(start.env)
		ifconfig ! ="" {
			// in normal operation, the start function never returns
			// if it does, exit! , this triggers a restart of the lambda function
            // start.f is func, lambda/invoke_loop.go: func startRuntimeAPILoop(CTX context. context, API string, handler Handler) error
			err := start.f(ctx, config, handler)
			logFatalf("%v", err)
		}
		keys = append(keys, start.env)
	}
	logFatalf("expected AWS Lambda environment variables %s are not defined", keys)
}
Copy the code

Next, look at the startRuntimeAPILoop method, which basically waits for the event request of the trigger before calling the handleInvoke method

func startRuntimeAPILoop(ctx context.Context, api string, handler Handler) error {
    // The API is the AWS_LAMBDA_RUNTIME_API, and it's created here at the url/Runtime/Invocation /
	client := newRuntimeAPIClient(api)
    // Function is a struct that encapsulates the handler, and defines Ping, Invoke, and Invoke methods
	function := NewFunction(handler).withContext(ctx)
	for {
        // The next method is added "Next" after the URL, which is "/ Runtime/Invocation /next" and will initiate the HTTP invocation. The API is used to wait for a new Invoke request, and the Invoke structure is returned. This contains the []byte of the trigger event
		invoke, err := client.next()
		iferr ! =nil {
			return err
		}

		err = handleInvoke(invoke, function)
		iferr ! =nil {
			return err
		}
	}
}
Copy the code

Convert the Invoke method to InvokeRequest, and then invoke the Function

func handleInvoke(invoke *invoke, function *Function) error {
    // Convert invoke to *messages.InvokeRequest, There was lambda-runtime-cognito -Identity, lambda-runtime-trace-id, lambda-runtime-invoked - function-arn, and the like in the request header, See [AWS Lambda Runtime API] (https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html)
	functionRequest, err := convertInvokeRequest(invoke)
	iferr ! =nil {
		return fmt.Errorf("unexpected error occurred when parsing the invoke: %v", err)
	}

	functionResponse := &messages.InvokeResponse{}
    // This is where the request is passed to call the Invoke method of the Function. The Invoke method is mainly the CTX operation, and the handler is actually called
	iferr := function.Invoke(functionRequest, functionResponse); err ! =nil {
		return fmt.Errorf("unexpected error occurred when invoking the handler: %v", err)
	}

	iffunctionResponse.Error ! =nil {
		payload := safeMarshal(functionResponse.Error)
		iferr := invoke.failure(payload, contentTypeJSON); err ! =nil {
			return fmt.Errorf("unexpected error occurred when sending the function error to the API: %v", err)
		}
		if functionResponse.Error.ShouldExit {
			return fmt.Errorf("calling the handler function resulted in a panic, the process should exit")}return nil
	}

    / / will function execution results through/runtime invocation/response is returned to the caller
	iferr := invoke.success(functionResponse.Payload, contentTypeJSON); err ! =nil {
		return fmt.Errorf("unexpected error occurred when sending the function functionResponse to the API: %v", err)
	}

	return nil
}
Copy the code

We’re going to look directly at the Handler’s Invoke method, which is an interface, and we’re going to look at the lambdaHandler implemented inside, which is going to call the original handler and serialize the response

// lambda/handler.go
func (handler lambdaHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) {
    // Handler is the basic lambda function returned by NewHandler. Payload is the event of the previous trigger
	response, err := handler(ctx, payload)
	iferr ! =nil {
		return nil, err
	}

	responseBytes, err := json.Marshal(response)
	iferr ! =nil {
		return nil, err
	}

	return responseBytes, nil
}
Copy the code

At this point, the logic for calling a function for AWS Lambda is clear.