×

先决条件

TypeScript 函数模板结构

使用 Knative (kn) CLI 创建 TypeScript 函数时,项目目录看起来像一个典型的 TypeScript 项目。唯一的例外是额外的 func.yaml 文件,该文件用于配置函数。

httpevent 触发函数具有相同的模板结构。

模板结构
.
├── func.yaml (1)
├── package.json (2)
├── package-lock.json
├── README.md
├── src
│   └── index.ts (3)
├── test (4)
│   ├── integration.ts
│   └── unit.ts
└── tsconfig.json
1 func.yaml 配置文件用于确定镜像名称和注册表。
2 您不限于模板 package.json 文件中提供的依赖项。您可以像在任何其他 TypeScript 项目中一样添加其他依赖项。
添加 npm 依赖项的示例
npm install --save opossum

构建项目以进行部署时,这些依赖项将包含在创建的运行时容器镜像中。

3 您的项目必须包含一个导出名为 handle 的函数的 src/index.js 文件。
4 集成和单元测试脚本作为函数模板的一部分提供。

关于调用 TypeScript 函数

使用 Knative (kn) CLI 创建函数项目时,您可以生成一个响应 CloudEvents 的项目,或一个响应简单 HTTP 请求的项目。Knative 中的 CloudEvents 通过 HTTP 作为 POST 请求传输,因此两种类型的函数都侦听并响应传入的 HTTP 事件。

TypeScript 函数可以使用简单的 HTTP 请求调用。收到传入请求时,将使用 context 对象作为第一个参数调用函数。

TypeScript 上下文对象

要调用函数,请提供 context 对象作为第一个参数。访问 context 对象的属性可以提供有关传入 HTTP 请求的信息。

上下文对象示例
function handle(context:Context): string

此信息包括 HTTP 请求方法、随请求发送的任何查询字符串或标头、HTTP 版本以及请求正文。包含CloudEvent的传入请求会将传入的 CloudEvent 实例附加到上下文对象,以便可以使用context.cloudevent访问它。

上下文对象方法

context 对象有一个方法cloudEventResponse(),它接受一个数据值并返回一个 CloudEvent。

在 Knative 系统中,如果作为服务部署的函数由发送 CloudEvent 的事件代理调用,则代理会检查响应。如果响应是 CloudEvent,则此事件将由代理处理。

上下文对象方法示例
// Expects to receive a CloudEvent with customer data
export function handle(context: Context, cloudevent?: CloudEvent): CloudEvent {
  // process the customer
  const customer = cloudevent.data;
  const processed = processCustomer(customer);
  return context.cloudEventResponse(customer)
    .source('/customer/process')
    .type('customer.processed')
    .response();
}

上下文类型

TypeScript 类型定义文件导出以下类型供您的函数使用。

导出的类型定义
// Invokable is the expeted Function signature for user functions
export interface Invokable {
    (context: Context, cloudevent?: CloudEvent): any
}

// Logger can be used for structural logging to the console
export interface Logger {
  debug: (msg: any) => void,
  info:  (msg: any) => void,
  warn:  (msg: any) => void,
  error: (msg: any) => void,
  fatal: (msg: any) => void,
  trace: (msg: any) => void,
}

// Context represents the function invocation context, and provides
// access to the event itself as well as raw HTTP objects.
export interface Context {
    log: Logger;
    req: IncomingMessage;
    query?: Record<string, any>;
    body?: Record<string, any>|string;
    method: string;
    headers: IncomingHttpHeaders;
    httpVersion: string;
    httpVersionMajor: number;
    httpVersionMinor: number;
    cloudevent: CloudEvent;
    cloudEventResponse(data: string|object): CloudEventResponse;
}

// CloudEventResponse is a convenience class used to create
// CloudEvents on function returns
export interface CloudEventResponse {
    id(id: string): CloudEventResponse;
    source(source: string): CloudEventResponse;
    type(type: string): CloudEventResponse;
    version(version: string): CloudEventResponse;
    response(): CloudEvent;
}

CloudEvent 数据

如果传入请求是 CloudEvent,则会从事件中提取与 CloudEvent 关联的任何数据,并作为第二个参数提供。例如,如果收到一个 CloudEvent,其 data 属性中包含类似于以下内容的 JSON 字符串

{
  "customerId": "0123456",
  "productId": "6543210"
}

调用时,函数的第二个参数(在context对象之后)将是一个具有customerIdproductId属性的 JavaScript 对象。

示例签名
function handle(context: Context, cloudevent?: CloudEvent): CloudEvent

在此示例中,cloudevent参数是一个包含customerIdproductId属性的 JavaScript 对象。

TypeScript 函数返回值

函数可以返回任何有效的 JavaScript 类型,也可以没有返回值。当函数没有指定返回值且没有指示失败时,调用方会收到204 No Content响应。

函数还可以返回 CloudEvent 或Message对象,以便将事件推送到 Knative Eventing 系统。在这种情况下,开发人员无需理解或实现 CloudEvent 消息传递规范。将提取返回值中的标头和其他相关信息,并随响应一起发送。

示例
export const handle: Invokable = function (
  context: Context,
  cloudevent?: CloudEvent
): Message {
  // process customer and return a new CloudEvent
  const customer = cloudevent.data;
  return HTTP.binary(
    new CloudEvent({
      source: 'customer.processor',
      type: 'customer.processed'
    })
  );
};

返回标头

您可以通过向return对象添加headers属性来设置响应标头。这些标头将被提取并随响应一起发送给调用方。

响应标头示例
export function handle(context: Context, cloudevent?: CloudEvent): Record<string, any> {
  // process customer and return custom headers
  const customer = cloudevent.data as Record<string, any>;
  return { headers: { 'customer-id': customer.id } };
}

返回状态代码

您可以通过向return对象添加statusCode属性来设置返回给调用方的状态代码。

状态代码示例
export function handle(context: Context, cloudevent?: CloudEvent): Record<string, any> {
  // process customer
  const customer = cloudevent.data as Record<string, any>;
  if (customer.restricted) {
    return {
      statusCode: 451
    }
  }
  // business logic, then
  return {
    statusCode: 240
  }
}

也可以为函数创建和抛出的错误设置状态代码。

错误状态代码示例
export function handle(context: Context, cloudevent?: CloudEvent): Record<string, string> {
  // process customer
  const customer = cloudevent.data as Record<string, any>;
  if (customer.restricted) {
    const err = new Error(Unavailable for legal reasons);
    err.statusCode = 451;
    throw err;
  }
}

测试 TypeScript 函数

可以在您的计算机上本地测试 TypeScript 函数。使用kn func create创建函数时创建的默认项目中,有一个包含一些简单的单元测试和集成测试的test文件夹。

先决条件
  • 集群上已安装 OpenShift Serverless Operator 和 Knative Serving。

  • 您已安装 Knative (kn) CLI。

  • 您已使用kn func create创建了一个函数。

步骤
  1. 如果您以前没有运行过测试,请先安装依赖项。

    $ npm install
  2. 导航到函数的test文件夹。

  3. 运行测试

    $ npm test

覆盖存活性探针和就绪探针值

您可以覆盖 TypeScript 函数的livenessreadiness探针值。这允许您配置对函数执行的运行状况检查。

先决条件
  • 集群上已安装 OpenShift Serverless Operator 和 Knative Serving。

  • 您已安装 Knative (kn) CLI。

  • 您已使用kn func create创建了一个函数。

步骤
  1. 在您的函数代码中,创建Function对象,它实现了以下接口

    export interface Function {
      init?: () => any; (1)
    
      shutdown?: () => any; (2)
    
      liveness?: HealthCheck; (3)
    
      readiness?: HealthCheck; (4)
    
      logLevel?: LogLevel;
    
      handle: CloudEventFunction | HTTPFunction; (5)
    }
    1 初始化函数,在服务器启动之前调用。此函数是可选的,应该是同步的。
    2 关闭函数,在服务器停止后调用。此函数是可选的,应该是同步的。
    3 存活性函数,用于检查服务器是否处于活动状态。此函数是可选的,如果服务器处于活动状态,则应返回 200/OK。
    4 就绪函数,用于检查服务器是否已准备好接受请求。此函数是可选的,如果服务器已准备好,则应返回 200/OK。
    5 用于处理 HTTP 请求的函数。

    例如,将以下代码添加到index.js文件中

    const Function = {
    
      handle: (context, body) => {
        // The function logic goes here
        return 'function called'
      },
    
      liveness: () => {
        process.stdout.write('In liveness\n');
        return 'ok, alive';
      }, (1)
    
      readiness: () => {
        process.stdout.write('In readiness\n');
        return 'ok, ready';
      } (2)
    };
    
    Function.liveness.path = '/alive'; (3)
    Function.readiness.path = '/ready'; (4)
    
    module.exports = Function;
    1 自定义liveness函数。
    2 自定义readiness函数。
    3 自定义liveness端点。
    4 自定义readiness端点。

    作为Function.liveness.pathFunction.readiness.path的替代方法,您可以使用LIVENESS_URLREADINESS_URL环境变量指定自定义端点。

    run:
      envs:
      - name: LIVENESS_URL
        value: /alive (1)
      - name: READINESS_URL
        value: /ready (2)
    1 存活性路径,此处设置为/alive
    2 就绪路径,此处设置为/ready
  2. 将新的端点添加到func.yaml文件中,以便它们正确绑定到 Knative 服务的容器。

    deploy:
      healthEndpoints:
        liveness: /alive
        readiness: /ready

TypeScript 上下文对象参考

context对象有一些函数开发人员可以访问的属性。访问这些属性可以提供有关传入 HTTP 请求的信息并将输出写入集群日志。

log

提供一个日志记录对象,可用于将输出写入集群日志。该日志符合Pino 日志 API

日志示例
export function handle(context: Context): string {
    // log the incoming request body's 'hello' parameter
    if (context.body) {
      context.log.info((context.body as Record<string, string>).hello);
    } else {
      context.log.info('No data received');
    }
    return 'OK';
}

您可以使用kn func invoke命令访问该函数。

命令示例
$ kn func invoke --target 'http://example.function.com'
输出示例
{"level":30,"time":1604511655265,"pid":3430203,"hostname":"localhost.localdomain","reqId":1,"msg":"Processing customer"}

您可以将日志级别更改为fatalerrorwarninfodebugtracesilent之一。为此,请通过使用config命令将这些值之一分配给环境变量FUNC_LOG_LEVEL来更改logLevel的值。

query

返回请求的查询字符串(如果有),作为键值对。这些属性也位于上下文对象本身中。

查询示例
export function handle(context: Context): string {
      // log the 'name' query parameter
    if (context.query) {
      context.log.info((context.query as Record<string, string>).name);
    } else {
      context.log.info('No data received');
    }
    return 'OK';
}

您可以使用kn func invoke命令访问该函数。

命令示例
$ kn func invoke --target 'http://example.function.com' --data '{"name": "tiger"}'
输出示例
{"level":30,"time":1604511655265,"pid":3430203,"hostname":"localhost.localdomain","reqId":1,"msg":"tiger"}
{"level":30,"time":1604511655265,"pid":3430203,"hostname":"localhost.localdomain","reqId":1,"msg":"tiger"}

body

返回请求正文(如果有)。如果请求正文包含 JSON 代码,则会对其进行解析,以便可以直接使用属性。

正文示例
export function handle(context: Context): string {
    // log the incoming request body's 'hello' parameter
    if (context.body) {
      context.log.info((context.body as Record<string, string>).hello);
    } else {
      context.log.info('No data received');
    }
    return 'OK';
}

您可以使用kn func invoke命令访问该函数。

命令示例
$ kn func invoke --target 'http://example.function.com' --data '{"hello": "world"}'
输出示例
{"level":30,"time":1604511655265,"pid":3430203,"hostname":"localhost.localdomain","reqId":1,"msg":"world"}

headers

将 HTTP 请求标头作为对象返回。

标头示例
export function handle(context: Context): string {
    // log the incoming request body's 'hello' parameter
    if (context.body) {
      context.log.info((context.headers as Record<string, string>)['custom-header']);
    } else {
      context.log.info('No data received');
    }
    return 'OK';
}

您可以使用curl命令调用该函数来访问它。

命令示例
$ curl -H'x-custom-header: some-value’' http://example.function.com
输出示例
{"level":30,"time":1604511655265,"pid":3430203,"hostname":"localhost.localdomain","reqId":1,"msg":"some-value"}

HTTP 请求

method

将 HTTP 请求方法作为字符串返回。

httpVersion

将 HTTP 版本作为字符串返回。

httpVersionMajor

将 HTTP 主版本号作为字符串返回。

httpVersionMinor

将 HTTP 次版本号作为字符串返回。

后续步骤