11.3. 开发类型脚本功能

创建 TypeScript 功能项目 后,您可以修改提供的模板文件,以将业务逻辑添加到您的功能中。这包括配置功能调用和返回的标头和状态代码。

11.3.1. 先决条件

11.3.2. 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 配置文件用于决定镜像名称和 registry。
2
您不限于模板 package.json 文件中提供的依赖项。您可以添加额外的依赖项,如任何其他 TypeScript 项目中一样。

添加 npm 依赖项示例

npm install --save opossum

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

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

11.3.3. 关于调用 TypeScript 函数

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

TypeScript 函数可通过简单的 HTTP 请求调用。收到传入请求后,将通过 上下文 对象作为第一个参数来调用函数。

11.3.3.1. TypeScript 上下文对象

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

上下文对象示例

function handle(context:Context): string

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

11.3.3.1.1. 上下文对象方法

上下文(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();
}

11.3.3.1.2. 上下文类型

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;
}

11.3.3.1.3. CloudEvent 数据

如果传入的请求为 CloudEvent,则从事件中提取与 CloudEvent 相关的任何数据,并作为第二个参数提供。例如,如果收到在它的数据属性中包含类似如下的 JSON 字符串的 CloudEvent:

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

在调用时。函数的第二个参数(在上下文 对象后),将是带有 customerIdproductId 属性的 JavaScript 对象。

签名示例

function handle(context: Context, cloudevent?: CloudEvent): CloudEvent

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

11.3.4. 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'
    })
  );
};

11.3.4.1. 返回的标头

您可以通过在 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 } };
}

11.3.4.2. 返回状态代码

您可以通过在返回对象中添加 statusCode 属性来设置 return 到调用者的状态代码:

状态代码示例

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;
  }
}

11.3.5. 测试类型脚本功能

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

先决条件

  • 在集群中安装了 OpenShift Serverless Operator 和 Knative Serving。
  • 已安装 Knative (kn) CLI。
  • 已使用 kn func create 创建功能。

流程

  1. 如果您之前还没有运行测试,请首先安装依赖项:

    $ npm install
  2. 导航到您的功能的 test 文件夹。
  3. 运行测试:

    $ npm test

11.3.6. 覆盖存活度和就绪度探测值

您可以覆盖 TypeScript 功能的 存活度和就绪度探测 值。这可让您配置在功能上执行的健康检查。

先决条件

  • 在集群中安装了 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
    自定义 存活度 功能。
    2
    自定义 就绪度 功能。
    3
    自定义 存活度 端点。
    4
    自定义 就绪度 端点。

    作为 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

11.3.7. TypeScript 上下文对象引用

上下文 对象具有多个属性,可供函数开发人员访问。访问这些属性可提供有关传入 HTTP 请求的信息,并将输出写入集群日志。

11.3.7.1. 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 的值。

11.3.7.2. 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"}

11.3.7.3. 正文(body)

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

body 示例

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"}

11.3.7.4. 标头

将 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"}

11.3.7.5. HTTP 请求

方法
以字符串形式返回 HTTP 请求方法。
httpVersion
以字符串形式返回 HTTP 版本。
httpVersionMajor
将 HTTP 主版本号返回为字符串。
httpVersionMinor
以字符串形式返回 HTTP 次要版本号。

11.3.8. 后续步骤