Este conteúdo foi traduzido por máquina para sua conveniência e a Huawei Cloud não pode garantir que o conteúdo foi traduzido com precisão. Para exibir o conteúdo original, use o link no canto superior direito para mudar para a página em inglês.
Atualizado em 2022-11-16 GMT+08:00

Tempo de execução personalizado

Cenários

Um tempo de execução executa o código de uma função, lê o nome do manipulador de uma variável de ambiente e lê eventos de invocação das API de tempo de execução do FunctionGraph. O tempo de execução passa os dados do evento para o manipulador da função e retorna a resposta do manipulador para FunctionGraph.

O FunctionGraph oferece suporte a tempos de execução personalizados. Você pode usar um arquivo executável chamado bootstrap para incluir um runtime no pacote de implantação da função. O tempo de execução executa o método manipulador da função quando a função é invocada.

Seu tempo de execução é executado no ambiente de execução do FunctionGraph. Pode ser um script shell ou um arquivo executável binário compilado no Linux.

Após a programação, basta empacotar seu código em um arquivo ZIP (Java, Node.js, Python e Go) ou arquivo JAR (Java) e fazer o upload do arquivo para FunctionGraph para execução. Ao criar um arquivo ZIP, coloque o arquivo do manipulador sob o diretório root para garantir que seu código possa ser executado normalmente após ser descompactado.

Se você editar código em Go, compacte o arquivo compilado e verifique se o nome do arquivo de biblioteca dinâmica é consistente com o nome do plug-in do manipulador. Por exemplo, se o nome do arquivo de biblioteca dinâmica for testplugin.so, defina o nome do manipulador como testplugin.Handler.

Bootstrap do arquivo de tempo de execução

Se houver um arquivo chamado bootstrap no pacote de implantação da função, o FunctionGraph executará esse arquivo. Se o arquivo bootstrap não for encontrado ou não for executável, sua função retornará um erro quando for invocada.

O código de tempo de execução é responsável por concluir as tarefas de inicialização. Ele processa eventos de invocação em um loop até que seja encerrado.

As tarefas de inicialização são executadas uma vez para cada instância da função para preparar o ambiente para manipulação de invocações.

As API de tempo de execução

O FunctionGraph fornece as API de tempo de execução HTTP para receber eventos de chamada de função e retornar dados de resposta no ambiente de execução.

  • Próxima invocação

    Método – Get

    Caminho – http://$RUNTIME_API_ADDR/v1/runtime/invocation/request

    Essa API é usada para recuperar um evento de invocação. O corpo da resposta contém os dados do evento. A tabela a seguir descreve dados adicionais sobre a invocação contida no cabeçalho da resposta.

    Tabela 1 Informações de cabeçalho de resposta

    Parâmetro

    Descrição

    X-Cff-Request-Id

    Solicitar ID.

    X-CFF-Access-Key

    AK da conta. Uma agência deve ser configurada para a função se essa variável for usada.

    X-CFF-Auth-Token

    Token da conta. Uma agência deve ser configurada para a função se essa variável for usada.

    X-CFF-Invoke-Type

    Tipo de invocação da função.

    X-CFF-Secret-Key

    SK da conta. Uma agência deve ser configurada para a função se essa variável for usada.

    X-CFF-Security-Token

    Token de segurança da conta. Uma agência deve ser configurada para a função se essa variável for usada.

  • Resposta de invocação

    Método – POST

    Caminho – http://$RUNTIME_API_ADDR/v1/runtime/invocation/response/$REQUEST_ID

    Essa API é usada para enviar uma resposta de invocação bem-sucedida para a FunctionGraph. Depois que o tempo de execução invoca o manipulador da função, ele publica a resposta da função no caminho de resposta da invocação.

  • Erro de invocação

    Método – POST

    Caminho – http://$RUNTIME_API_ADDR/v1/runtime/invocation/error/$REQUEST_ID

    $REQUEST_ID é o valor da variável X-Cff-Request-Id no cabeçalho de uma resposta de recuperação de evento. Para obter mais informações, consulte Tabela 1.

    $RUNTIME_API_ADDR é uma variável de ambiente do sistema. Para obter mais informações, consulte Tabela 2.

    Essa API é usada para enviar uma resposta de invocação de erro para a FunctionGraph. Depois que o tempo de execução invoca o manipulador da função, ele publica a resposta da função no caminho de resposta da invocação.

Variáveis de ambiente de tempo de execução

Você pode usar variáveis de ambiente personalizadas e de tempo de execução no código de função. A tabela a seguir lista as variáveis de ambiente de tempo de execução usadas no ambiente de execução do FunctionGraph.

Tabela 2 Variáveis de ambiente

Chave

Descrição

RUNTIME_PROJECT_ID

ID do projeto

RUNTIME_FUNC_NAME

Nome da função

RUNTIME_FUNC_VERSION

Versão da função

RUNTIME_PACKAGE

Aplicativo ao qual a função pertence

RUNTIME_HANDLER

Manipulador de funções

RUNTIME_TIMEOUT

Duração do timeout da função

RUNTIME_USERDATA

Valor passado por uma variável de ambiente

RUNTIME_CPU

Número de núcleos de CPU alocados

RUNTIME_MEMORY

Memória alocada

RUNTIME_CODE_ROOT

Diretório que armazena o código da função

RUNTIME_API_ADDR

Endereço IP do host e porta de uma API de tempo de execução personalizada

O valor de uma variável de ambiente personalizada pode ser recuperado da mesma forma que o valor de uma variável de ambiente FunctionGraph.

Exemplo

Este exemplo contém um arquivo chamado bootstrap. O arquivo é implementado em Bash.

O tempo de execução carrega o script de função do pacote de implantação usando duas variáveis.

O arquivo bootstrap é o seguinte:

#!/bin/sh

set -o pipefail

#Processing requests loop
while true
do
 
HEADERS="$(mktemp)"
  # Get an event
  EVENT_DATA=$(curl
-sS -LD "$HEADERS" -X GET
"http://$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: '$EVENT_DATA'"
  # Put response
  curl -X POST
"http://$RUNTIME_API_ADDR/v1/runtime/invocation/response/$REQUEST_ID"
-d "$RESPONSE"
done

Após carregar o script, o runtime processa eventos de invocação em um loop até que ele seja encerrado. Ele usa a API para recuperar eventos de invocação do FunctionGraph e envia as respostas para FunctionGraph.

Para obter a ID da solicitação, o runtime salva o cabeçalho da resposta da API em um arquivo temporário e lê a ID da solicitação no campo do cabeçalho x-cff-request-id. O tempo de execução processa os dados do evento recuperados e envia uma resposta de volta para FunctionGraph.

A seguir, um exemplo de código-fonte em Go. Ele pode ser executado somente após a compilação.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net"
    "net/http"
    "os"
    "strings"
    "time"
)

var (
    getRequestUrl           = os.ExpandEnv("http://${RUNTIME_API_ADDR}/v1/runtime/invocation/request")
    putResponseUrl          = os.ExpandEnv("http://${RUNTIME_API_ADDR}/v1/runtime/invocation/response/{REQUEST_ID}")
    putErrorResponseUrl     = os.ExpandEnv("http://${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
}

Tabela 3 descreve as variáveis de ambiente usadas no código anterior.

Tabela 3 Variáveis de ambiente

Variável de ambiente

Descrição

RUNTIME_FUNC_NAME

Nome da função

RUNTIME_FUNC_VERSION

Versão da função

RUNTIME_PACKAGE

Aplicativo ao qual a função pertence