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:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
)
func handler(w http.ResponseWriter, req *http.Request) {
requestID := req.Header.Get("x-fc-request-id")
fmt.Println(fmt.Sprintf("FC Invoke Start RequestId: %s", requestID))
defer func() {
fmt.Println(fmt.Sprintf("FC Invoke End RequestId: %s", requestID))
}()
// your logic
b, err := ioutil.ReadAll(req.Body)
if err != nil {
panic(err)
}
info := fmt.Sprintf("method = %+v;\nheaders = %+v;\nbody = %+v", req.Method, req.Header, string(b))
w.Write([]byte(fmt.Sprintf("Hello, golang http invoke! detail:\n %s", info)))
}
func main() {
fmt.Println("FunctionCompute go runtime inited.")
http.HandleFunc("/", handler)
port := os.Getenv("FC_SERVER_PORT")
if port == "" {
port = "9000"
}
http.ListenAndServe(":" + port, nil)
}
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:
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
CRService:
Type: 'Aliyun::Serverless::Service'
Properties:
Description: 'custom runtime demo'
hello:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: index.handler
CodeUri: ./code.zip
Description: 'demo with custom runtime'
Runtime: custom
Events:
http_t:
Type: HTTP
Properties:
AuthType: ANONYMOUS
Methods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD']
goexample.abc.cn:
Type: 'Aliyun::Serverless::CustomDomain'
Properties:
Protocol: HTTP
RouteConfig:
Routes:
'/*':
ServiceName: CRService
FunctionName: hello
Note: In this case, the value of
Handler
is meaningless. You can enter any string such asindex.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:
func handler(ctx *FCContext, event []byte) ([]byte, error)
func initialize(ctx *gr.FCContext) error
package main
import (
"encoding/json"
gr "github.com/awesome-fc/golang-runtime"
)
func initialize(ctx *gr.FCContext) error {
fcLogger := gr.GetLogger().WithField("requestId", ctx.RequestID)
fcLogger.Infoln("init golang!")
return nil
}
func handler(ctx *gr.FCContext, event []byte) ([]byte, error) {
fcLogger := gr.GetLogger().WithField("requestId", ctx.RequestID)
b, err := json.Marshal(ctx)
if err != nil {
fcLogger.Error("error:", err)
}
fcLogger.Infof("hello golang! \ncontext = %s", string(b))
return event, nil
}
func main() {
gr.Start(handler, initialize)
}
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:
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
auto-op-demo-pro:
Type: 'Aliyun::Serverless::Log'
Properties:
Description: 'custom runtime log pro'
fc-log:
Type: 'Aliyun::Serverless::Log::Logstore'
Properties:
TTL: 362
ShardCount: 1
CRService:
Type: 'Aliyun::Serverless::Service'
Properties:
Description: 'custom runtime demo'
Policies:
- AliyunOSSFullAccess
LogConfig:
Project: 'auto-op-demo-pro'
Logstore: 'fc-log'
hello:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: index.handler
Initializer: index.initializer
CodeUri: ./hello.zip
Description: 'demo with custom runtime'
Runtime: custom
Note: In this case, the values of
Handler
andInitializer
are meaningless. You can enter any string such asindex.handler
orindex.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:
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
controlPath := req.Header.Get(fcControlPath)
if controlPath == "/initialize" {
initializeHandler(w, req)
} else {
invokeHandler(w, req)
}
2. Construct request parameters context and event
ctx := &FCContext{
RequestID: req.Header.Get(fcRequestID),
Credentials: Credentials{
AccessKeyID: req.Header.Get(fcAccessKeyID),
AccessKeySecret: req.Header.Get(fcAccessKeySecret),
SecurityToken: req.Header.Get(fcSecurityToken),
},
Function: FunctionMeta{
Name: req.Header.Get(fcFunctionName),
Handler: req.Header.Get(fcFunctionHandler),
Memory: m,
Timeout: t,
Initializer: req.Header.Get(fcFunctionInitializer),
InitializationTimeout: it,
},
Service: ServiceMeta{
ServiceName: req.Header.Get(fcServiceName),
LogProject: req.Header.Get(fcServiceLogProject),
LogStore: req.Header.Get(fcServiceLogstore),
Qualifier: req.Header.Get(fcQualifier),
VersionID: req.Header.Get(fcVersionID),
},
Region: req.Header.Get(fcRegion),
AccountID: req.Header.Get(fcAccountID),
}
event, err := ioutil.ReadAll(req.Body)
if err != nil {
panic(err)
}
3. Check the x-fc-status field and specify the log format
func invokeHandler(w http.ResponseWriter, req *http.Request) {
requestID := req.Header.Get(fcRequestID)
fmt.Println(fmt.Sprintf(fcLogTailStartPrefix, requestID))
defer func() {
if r := recover(); r != nil {
w.Header().Set(fcStatus, "404")
w.Write([]byte(fmt.Sprintf("Error: %+v;\nStack: %s", r, string(debug.Stack()))))
}
fmt.Println(fmt.Sprintf(fcLogTailEndPrefix, requestID))
}()
...
w.Write([]byte(resp))
}
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 thex-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.
Use the recommended log format to include the time in UTC and the request ID
log = &logrus.Logger{
Out: os.Stderr,
Level: logrus.InfoLevel,
Formatter: &UTCFormatter{
TimestampFormat: "2006-01-02T15:04:05.999Z",
LogFormat: "%time%: %requestId% [%lvl%] %msg%\n",
},
},
}