All Products
Search
Document Center

Create a golang runtime based on a custom runtime

Last Updated: Feb 20, 2020

Custom runtime manual describes the basic principles and specifications of custom runtimes. To help you better understand custom runtimes, this topic describes how to build a Golang runtime and call functions.

HTTP trigger calls

As described in Custom runtime manual, if you require an application that uses HTTP trigger calls, you can develop it based on a custom runtime or quickly port an existing web application to Function Compute. For example, use the following sample code to develop a simple web application:

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "net/http"
  6. "os"
  7. )
  8. func handler(w http.ResponseWriter, req *http.Request) {
  9. requestID := req.Header.Get("x-fc-request-id")
  10. fmt.Println(fmt.Sprintf("FC Invoke Start RequestId: %s", requestID))
  11. defer func() {
  12. fmt.Println(fmt.Sprintf("FC Invoke End RequestId: %s", requestID))
  13. }()
  14. // your logic
  15. b, err := ioutil.ReadAll(req.Body)
  16. if err != nil {
  17. panic(err)
  18. }
  19. info := fmt.Sprintf("method = %+v;\nheaders = %+v;\nbody = %+v", req.Method, req.Header, string(b))
  20. w.Write([]byte(fmt.Sprintf("Hello, golang http invoke! detail:\n %s", info)))
  21. }
  22. func main() {
  23. fmt.Println("FunctionCompute go runtime inited.")
  24. http.HandleFunc("/", handler)
  25. port := os.Getenv("FC_SERVER_PORT")
  26. if port == "" {
  27. port = "9000"
  28. }
  29. http.ListenAndServe(":" + port, nil)
  30. }

Compile the preceding code as an executable file.

Note: The docker image of custom runtimes is based on Debian 9 (Stretch). We recommend that you use golang:1.12.9-stretch. To pull the docker image, you can run the docker pull golang:1.12.9-stretch command. For more information, see Custom runtime manual.

Name this executable file bootstrap, package the bootstrap file into a code.zip file, and then use Fun to deploy the application.

The following example shows the sample code in a template.yml file:

  1. ROSTemplateFormatVersion: '2015-09-01'
  2. Transform: 'Aliyun::Serverless-2018-04-03'
  3. Resources:
  4. CRService:
  5. Type: 'Aliyun::Serverless::Service'
  6. Properties:
  7. Description: 'custom runtime demo'
  8. hello:
  9. Type: 'Aliyun::Serverless::Function'
  10. Properties:
  11. Handler: index.handler
  12. CodeUri: ./code.zip
  13. Description: 'demo with custom runtime'
  14. Runtime: custom
  15. Events:
  16. http_t:
  17. Type: HTTP
  18. Properties:
  19. AuthType: ANONYMOUS
  20. Methods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD']
  21. goexample.abc.cn:
  22. Type: 'Aliyun::Serverless::CustomDomain'
  23. Properties:
  24. Protocol: HTTP
  25. RouteConfig:
  26. Routes:
  27. '/*':
  28. ServiceName: CRService
  29. FunctionName: hello

Note: In this case, the value of Handler is meaningless. You can enter any string such as index.handler that meets the character set constraints of Function Compute handlers. In addition, if you do not specify a custom domain name but use a Function Compute endpoint to make a call request, the path is in the following format: /2016-08-15/proxy/$serviceName/$functionName/$yourRealPath.

Common calls

If you make a common call request, you can also use the preceding method to bind the function to be called to the code and logic on the HTTP server. However, you have to compile complex code each time you create a function. We recommend that you compile the code based on a simple framework golang-runtime. You only need to define the handler and initializer of the function to be called and specify the defined handler and initializer in the main function.

You can use the same functions as those used in an official runtime to define the handler and initializer as follows:

  1. func handler(ctx *FCContext, event []byte) ([]byte, error)
  2. func initialize(ctx *gr.FCContext) error
  1. package main
  2. import (
  3. "encoding/json"
  4. gr "github.com/awesome-fc/golang-runtime"
  5. )
  6. func initialize(ctx *gr.FCContext) error {
  7. fcLogger := gr.GetLogger().WithField("requestId", ctx.RequestID)
  8. fcLogger.Infoln("init golang!")
  9. return nil
  10. }
  11. func handler(ctx *gr.FCContext, event []byte) ([]byte, error) {
  12. fcLogger := gr.GetLogger().WithField("requestId", ctx.RequestID)
  13. b, err := json.Marshal(ctx)
  14. if err != nil {
  15. fcLogger.Error("error:", err)
  16. }
  17. fcLogger.Infof("hello golang! \ncontext = %s", string(b))
  18. return event, nil
  19. }
  20. func main() {
  21. gr.Start(handler, initialize)
  22. }

Compile the preceding code as an executable file.

Note: The docker image of custom runtimes is based on Debian 9 (Stretch). We recommend that you use golang:1.12.9-stretch. To pull the docker image, you can run the docker pull golang:1.12.9-stretch command. For more information, see Custom runtime manual.

Name this executable file bootstrap, package the bootstrap file into a code.zip file, and then use Fun to deploy the application.

The following example shows the sample code in a template.yml file:

  1. ROSTemplateFormatVersion: '2015-09-01'
  2. Transform: 'Aliyun::Serverless-2018-04-03'
  3. Resources:
  4. auto-op-demo-pro:
  5. Type: 'Aliyun::Serverless::Log'
  6. Properties:
  7. Description: 'custom runtime log pro'
  8. fc-log:
  9. Type: 'Aliyun::Serverless::Log::Logstore'
  10. Properties:
  11. TTL: 362
  12. ShardCount: 1
  13. CRService:
  14. Type: 'Aliyun::Serverless::Service'
  15. Properties:
  16. Description: 'custom runtime demo'
  17. Policies:
  18. - AliyunOSSFullAccess
  19. LogConfig:
  20. Project: 'auto-op-demo-pro'
  21. Logstore: 'fc-log'
  22. hello:
  23. Type: 'Aliyun::Serverless::Function'
  24. Properties:
  25. Handler: index.handler
  26. Initializer: index.initializer
  27. CodeUri: ./hello.zip
  28. Description: 'demo with custom runtime'
  29. Runtime: custom

Note: In this case, the values of Handler and Initializer are meaningless. You can enter any string such as index.handler or index.initializer that meets the character set constraints of Function Compute handlers or initializers.

If the function is not called for the first time, you do not need to define the initializer. You can modify the logic in the main function as follows:

  1. gr.Start(handler, nil)

In addition, you do not need to specify the initializer when you create the function to be called.

For more information, see the source code of golang-runtime. Some features of the Golang runtime are described as follows:

1. Distinguish the handler from the initializer based on the value of the x-fc-control-path field

  1. controlPath := req.Header.Get(fcControlPath)
  2. if controlPath == "/initialize" {
  3. initializeHandler(w, req)
  4. } else {
  5. invokeHandler(w, req)
  6. }

2. Construct request parameters context and event

  1. ctx := &FCContext{
  2. RequestID: req.Header.Get(fcRequestID),
  3. Credentials: Credentials{
  4. AccessKeyID: req.Header.Get(fcAccessKeyID),
  5. AccessKeySecret: req.Header.Get(fcAccessKeySecret),
  6. SecurityToken: req.Header.Get(fcSecurityToken),
  7. },
  8. Function: FunctionMeta{
  9. Name: req.Header.Get(fcFunctionName),
  10. Handler: req.Header.Get(fcFunctionHandler),
  11. Memory: m,
  12. Timeout: t,
  13. Initializer: req.Header.Get(fcFunctionInitializer),
  14. InitializationTimeout: it,
  15. },
  16. Service: ServiceMeta{
  17. ServiceName: req.Header.Get(fcServiceName),
  18. LogProject: req.Header.Get(fcServiceLogProject),
  19. LogStore: req.Header.Get(fcServiceLogstore),
  20. Qualifier: req.Header.Get(fcQualifier),
  21. VersionID: req.Header.Get(fcVersionID),
  22. },
  23. Region: req.Header.Get(fcRegion),
  24. AccountID: req.Header.Get(fcAccountID),
  25. }
  1. event, err := ioutil.ReadAll(req.Body)
  2. if err != nil {
  3. panic(err)
  4. }

3. Check the x-fc-status field and specify the log format

  1. func invokeHandler(w http.ResponseWriter, req *http.Request) {
  2. requestID := req.Header.Get(fcRequestID)
  3. fmt.Println(fmt.Sprintf(fcLogTailStartPrefix, requestID))
  4. defer func() {
  5. if r := recover(); r != nil {
  6. w.Header().Set(fcStatus, "404")
  7. w.Write([]byte(fmt.Sprintf("Error: %+v;\nStack: %s", r, string(debug.Stack()))))
  8. }
  9. fmt.Println(fmt.Sprintf(fcLogTailEndPrefix, requestID))
  10. }()
  11. ...
  12. w.Write([]byte(resp))
  13. }

Use the x-fc-status field to indicate whether the function code and logic are run properly in the custom runtime

A value of 404 indicates that Function Compute fails to run the function. In this case, Function Compute also returns an UnHandled Error to the client that makes the call request, which is the same as that in an official runtime.

Return the runtime log of the function in the response

When you call a function and the request header contains the x-fc-log-type field whose value is Tail, the value of the x-fc-log-result field in the response header is the runtime log of the function.

FC Invoke Start RequestId: ${RequestId} and FC Invoke End RequestId: ${RequestId} are required log entries generated at the start and end of each request, respectively.

  1. log = &logrus.Logger{
  2. Out: os.Stderr,
  3. Level: logrus.InfoLevel,
  4. Formatter: &UTCFormatter{
  5. TimestampFormat: "2006-01-02T15:04:05.999Z",
  6. LogFormat: "%time%: %requestId% [%lvl%] %msg%\n",
  7. },
  8. },
  9. }