Node.js語言
√表示支持,×表示不支持
| 語言版本 | 是否支持 |
|---|---|
| Node.js 6.10 | √ |
| Node.js 8.10 | √ |
| Node.js 10.16 | √ |
| Node.js 12.13 | √ |
| Node.js 14.18 | √ |
| Node.js 16.17 | √ |
| Node.js 18.15 | √ |
Python語言
√表示支持,×表示不支持
| 語言版本 | 是否支持 |
|---|---|
| Python 2.7 | √ |
| Python 3.6 | √ |
| Python 3.9 | √ |
| Python 3.10 | √ |
Java語言
√表示支持,×表示不支持
| 語言版本 | 是否支持 |
|---|---|
| Java 8 | √ |
| Java 11 | √ |
Go語言
√表示支持,×表示不支持
| 語言版本 | 是否支持 |
|---|---|
| Go 1.x | √ |
C# 語言
√表示支持,×表示不支持
| 語言版本 | FunctionGraph V1 | FunctionGraph V2 |
|---|---|---|
| C# (.NET Core 2.1) | √ | × |
| C# (.NET Core 3.1) | √ | × |
PHP 語言
√表示支持,×表示不支持
| 語言版本 | FunctionGraph V1 | FunctionGraph V2 |
|---|---|---|
| PHP 7.3 | √ | × |
定制運行時語言
場景說明
運行時負責運行函數的設置代碼、從環境變量讀取處理程序名稱以及從FunctionGraph運行時API讀取調用事件。運行時會將事件數據傳遞給函數處理程序,并將來自處理程序的響應返回給FunctionGraph。
FunctionGraph支持自定義編程語言運行時。您可以使用可執行文件(名稱為bootstrap)的形式將運行時包含在函數的程序包中,當調用一個FunctionGraph函數時,它將運行函數的處理程序方法。
自定義的運行時在FunctionGraph執行環境中運行,它可以是Shell腳本,也可以是可在linux可執行的二進制文件。
說明在本地開發程序之后打包,必須是ZIP包(Java、Node.js、Python、Go)或者JAR包(Java),上傳至FunctionGraph即可運行,無需其它的部署操作。制作ZIP包的時候,單函數入口文件必須在根目錄,保證解壓后,直接出現函數執行入口文件,才能正常運行。
對于Go runtime,必須在編譯之后打zip包,編譯后的動態庫文件名稱必須與函數執行入口的插件名稱保持一致,例如:動態庫名稱為testplugin.so,則“函數執行入口”命名為testplugin.Handler。
運行時文件bootstrap說明
如果程序包中存在一個名為bootstrap的文件,FunctionGraph將執行該文件。如果引導文件未找到或不是可執行文件,函數在調用后將返回錯誤。
運行時代碼負責完成一些初始化任務,它將在一個循環中處理調用事件,直到它被終止。
初始化任務將對函數的每個實例運行一次以準備用于處理調用的環境。
運行時接口說明
FunctionGraph提供了用于自定義運行時的HTTP API來接收來自函數的調用事件,并在FunctionGraph執行環境中發送回響應數據。
- 獲取調用
方法 – Get
路徑 – //$RUNTIME_API_ADDR/v1/runtime/invocation/request
該接口用來獲取下一個事件,響應正文包含事件數據。響應標頭包含信息如下。
響應標頭信息說明
| 參數 | 說明 |
|---|---|
| X-Cff-Request-Id | 請求ID。 |
| X-CFF-Access-Key | 租戶AccessKey,使用該特殊變量需要給函數配置委托。 |
| X-CFF-Auth-Token | Token,使用該特殊變量需要給函數配置委托。 |
| X-CFF-Invoke-Type | 函數執行類型。 |
| X-CFF-Secret-Key | 租戶SecretKey,使用該特殊變量需要給函數配置委托。 |
| X-CFF-Security-Token | Security token,使用該特殊變量需要給函數配置委托。 |
- 調用響應
方法 – POST
路徑 –//RUNTIME_API_ADDR/v1/runtime/invocation/response/REQUEST_ID
該接口將正確的調用響應發送到FunctionGraph。在運行時調用函數處理程序后,將來自函數的響應發布到調用響應路徑。
- 錯誤上報
方法 – POST
路徑 – //RUNTIME_API_ADDR/v1/runtime/invocation/error/REQUEST_ID
$REQUEST_ID為獲取事件的響應header中X-Cff-Request-Id變量值,說明請參見上表。
$RUNTIME_API_ADDR為系統環境變量,說明請參見下表。
該接口將錯誤的調用響應發送到FunctionGraph。在運行時調用函數處理程序后,將來自函數的響應發布到調用響應路徑。
運行時環境變量說明
下面是FunctionGraph執行環境中運行時相關的環境變量列表,除此之外,還有用戶自定義的環境變量,都可以在函數代碼中直接使用。
環境變量說明
| 鍵 | 值說明 |
|---|---|
| RUNTIME_PROJECT_ID | projectID |
| RUNTIME_FUNC_NAME | 函數名稱 |
| RUNTIME_FUNC_VERSION | 函數的版本 |
| RUNTIME_PACKAGE | 函數組 |
| RUNTIME_HANDLER | 函數執行入口 |
| RUNTIME_TIMEOUT | 函數超時時間 |
| RUNTIME_USERDATA | 用戶通過環境變量傳入的值 |
| RUNTIME_CPU | 分配的CPU數 |
| RUNTIME_MEMORY | 分配的內存 |
| RUNTIME_CODE_ROOT | 包含函數代碼的目錄 |
| RUNTIME_API_ADDR | 自定義運行時API的主機和端口 |
用戶定義的環境變量也同FunctionGraph環境變量一樣,可通過環境變量獲取方式直接獲取用戶定義環境變量。
示例說明
此示例包含1個文件(bootstrap文件),該文件都在Bash中實施。
運行時將從部署程序包加載函數腳本。它使用兩個變量來查找腳本。
引導文件bootstrap內容如下:
#!/bin/sh
set -o pipefail
#Processing requests loop
while true
do
HEADERS="$(mktemp)"
# Get an event
EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "//$RUNTIME_API_ADDR/v1/runtime/invocation/request")
# Get request id from response header
REQUEST_ID=$(grep -Fi x-cff-request-id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)
if [ -z "$REQUEST_ID" ]; then
continue
fi
# Process request data
RESPONSE="Echoing request: hello world!"
# Put response
curl -X POST "//$RUNTIME_API_ADDR/v1/runtime/invocation/response/$REQUEST_ID" -d "$RESPONSE"
done
加載腳本后,運行時將在一個循環中處理事件。它使用運行時API從FunctionGraph檢索調用事件,將事件傳遞到處理程序,并將響應發布回給FunctionGraph。
為了獲取請求ID,運行時會將來自API響應的標頭保存到臨時文件,并從該文件讀取x-cff-request-id讀取請求頭的請求唯一標識。將獲取到的事件數據做處理并響應發布返回FunctionGraph。
go源碼示例,需要通過編譯后才可執行。
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"strings"
"time"
)
var (
getRequestUrl = os.ExpandEnv("//${RUNTIME_API_ADDR}/v1/runtime/invocation/request")
putResponseUrl = os.ExpandEnv("//${RUNTIME_API_ADDR}/v1/runtime/invocation/response/{REQUEST_ID}")
putErrorResponseUrl = os.ExpandEnv("//${RUNTIME_API_ADDR}/v1/runtime/invocation/error/{REQUEST_ID}")
requestIdInvalidError = fmt.Errorf("request id invalid")
noRequestAvailableError = fmt.Errorf("no request available")
putResponseFailedError = fmt.Errorf("put response failed")
functionPackage = os.Getenv("RUNTIME_PACKAGE")
functionName = os.Getenv("RUNTIME_FUNC_NAME")
functionVersion = os.Getenv("RUNTIME_FUNC_VERSION")
client = http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 3 * time.Second,
}).DialContext,
},
}
)
func main() {
// main loop for processing requests.
for {
requestId, header, payload, err := getRequest()
if err != nil {
time.Sleep(50 * time.Millisecond)
continue
}
result, err := processRequestEvent(requestId, header, payload)
err = putResponse(requestId, result, err)
if err != nil {
log.Printf("put response failed, err: %s.", err.Error())
}
}
}
// event processing function
func processRequestEvent(requestId string, header http.Header, evtBytes []byte) ([]byte, error) {
log.Printf("processing request '%s'.", requestId)
result := fmt.Sprintf("function: %s:%s:%s, request id: %s, headers: %+v, payload: %s", functionPackage, functionName,
functionVersion, requestId, header, string(evtBytes))
var event FunctionEvent
err := json.Unmarshal(evtBytes, &event)
if err != nil {
return (&ErrorMessage{ErrorType: "invalid event", ErrorMessage: "invalid json formated event"}).toJsonBytes(), err
}
return (&APIGFormatResult{StatusCode: 200, Body: result}).toJsonBytes(), nil
}
func getRequest() (string, http.Header, []byte, error) {
resp, err := client.Get(getRequestUrl)
if err != nil {
log.Printf("get request error, err: %s.", err.Error())
return "", nil, nil, err
}
defer resp.Body.Close()
// get request id from response header
requestId := resp.Header.Get("X-CFF-Request-Id")
if requestId == "" {
log.Printf("request id not found.")
return "", nil, nil, requestIdInvalidError
}
payload, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("read request body error, err: %s.", err.Error())
return "", nil, nil, err
}
if resp.StatusCode != 200 {
log.Printf("get request failed, status: %d, message: %s.", resp.StatusCode, string(payload))
return "", nil, nil, noRequestAvailableError
}
log.Printf("get request ok.")
return requestId, resp.Header, payload, nil
}
func putResponse(requestId string, payload []byte, err error) error {
var body io.Reader
if payload != nil && len(payload) > 0 {
body = bytes.NewBuffer(payload)
}
url := ""
if err == nil {
url = strings.Replace(putResponseUrl, "{REQUEST_ID}", requestId, -1)
} else {
url = strings.Replace(putErrorResponseUrl, "{REQUEST_ID}", requestId, -1)
}
resp, err := client.Post(strings.Replace(url, "{REQUEST_ID}", requestId, -1), "", body)
if err != nil {
log.Printf("put response error, err: %s.", err.Error())
return err
}
defer resp.Body.Close()
responsePayload, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("read request body error, err: %s.", err.Error())
return err
}
if resp.StatusCode != 200 {
log.Printf("put response failed, status: %d, message: %s.", resp.StatusCode, string(responsePayload))
return putResponseFailedError
}
return nil
}
type FunctionEvent struct {
Type string `json:"type"`
Name string `json:"name"`
}
type APIGFormatResult struct {
StatusCode int `json:"statusCode"`
IsBase64Encoded bool `json:"isBase64Encoded"`
Headers map[string]string `json:"headers,omitempty"`
Body string `json:"body,omitempty"`
}
func (result *APIGFormatResult) toJsonBytes() []byte {
data, err := json.MarshalIndent(result, "", " ")
if err != nil {
return nil
}
return data
}
type ErrorMessage struct {
ErrorType string `json:"errorType"`
ErrorMessage string `json:"errorMessage"`
}
func (errMsg *ErrorMessage) toJsonBytes() []byte {
data, err := json.MarshalIndent(errMsg, "", " ")
if err != nil {
return nil
}
return data
}
代碼中的環境變量說明如下,請參見下表。
環境變量說明
| 環境變量 | 說明 |
|---|---|
| RUNTIME_FUNC_NAME | 函數名稱 |
| RUNTIME_FUNC_VERSION | 函數版本 |
| RUNTIME_PACKAGE | 函數組 |