跳到主要内容

自定义函数

如果我们的 API 中有个别功能无法通过现有的步骤类型完成,或是需要一些特定的库,那么这时候就需要自定义函数来实现了。

比如说 短信 应用的发送短信接口,需要一些特定的库来调用供应商接口,同时逻辑也比较复杂,所以这里我们就直接使用一个自定义函数去实现发短信的功能。

如图所示:

functions

invoke

自定义函数本质是 Serverless 的 FaaS 系统,就是现在比较火的函数计算。

使用限制#

zip 包大小不能超过 100MB,原始代码不超过 500MB。

函数运行环境默认内存为 512MB,如需更大请联系客服。

函数超时时间默认为 10 秒,如需长时间任务请使用任务流步骤。

进程和线程总数:1024。

函数数据传输最大 6MB。

最佳实践#

函数代码越小越快,所以尽量将代码编译为单文件。

例如 nodejs 函数使用webpackncc 等库将代码编译成单个 index.js

基本概念#

函数的入口格式为 index.handler,即文件名为 index,函数名为 handler

以 nodejs 为例,入口文件为代码包根目录下的 index.jsexports.handler 是实际调用函数。

函数第一个参数是入参信息,在控制台设置变量。如前面的 sendSms 图片所示,在函数内可以这样得到具体数据:

index.js
module.exports.handler = async function (event, _, callback) {  const {    smsProvider,    code,    phoneNumber,    content,    type = 'verify',    signName,    templateId,    tencentAppId,    keyId,    keySecret,  } = JSON.parse(event || {});
  callback(null, phoneNumber);};

执行环境#

engine

函数 runtime 有这几种,具体示例如下:

nodejs#

index.js
exports.handler = async function (event, _, callback) {  // event为函数入参JSON字符串  const eventObj = JSON.parse(event || {});  // 回调函数callback,接受JSON对象或字符串  callback(null, eventObj);  // 错误处理 callback('错误信息',null)};

custom: go c++ ruby dart 等#

Custom Runtime 本质上是一个 HTTP Server,您只需要搭建一个具有监听端口的 HTTP Server,创建一个启动目标 Server 的可执行文件bootstrap,然后将该文件和您的代码文件一起打成 ZIP 包,最后以该 ZIP 包作为代码包创建一个 Custom Runtime 的函数。

Custom Runtime 内置支持以下语言,您可以直接创建以下语言的 Custom Runtime,无需安装第三方解释器。

Python 3.7.4 版本 Node.js 10.16.2 版本 PHP 7.4.12 版本 Ruby 2.7 版本 PowerShell 7.1.0 版本 Open JDK 1.8.0(Open JDK Version 1.8.0_232)

  • Custom Runtime 启动的服务一定要监听 0.0.0.0:CAPort 或*:CAPort 端口。如果您使用 127.0.0.1:CAPort 端口,会导致请求超时。
  • Custom Runtime 的监听端口(CAPort) 是 9000。所以代码服务的监听端口也必须是 9000.
  • Custom Runtime 的 bootstrap 文件,如果是 Shell 脚本一定要添加#!/bin/bash,如果是二进制可执行文件,例如 Go,C++直接编译出来的目标二进制文件,则不需要添加。
  • Custom Runtime 的 bootstrap 文件,一定要具备 777 或 755 权限。在打包文件前执行chmod 777 bootstrapchmod 755 bootstrap命令。如果您使用的是 Windows 操作系统,您需要将bootstrap文件格式修改为 UNIX 格式。
  • HTTP Server 需要在 30 秒内启动完毕

具体各语言代码示例可以参考这里

python#

index.py
import jsondef handler(event):    evt = json.loads(event)    return evt['key']

php#

index.php
<?phpfunction handler($event) {     $eventObj = json_decode($event, $assoc = true);     return $eventObj['key'];}

java#

您在使用 Java 编程时,必须要实现函数计算提供的接口类,对于事件入口函数目前有两个预定义接口可以选择。这两个预定义接口分别是:

StreamRequestHandler

以流的方式接受调用输入 event 和返回执行结果,您需要从输入流中读取调用函数时的输入,处理完成后把函数执行结果写入到输出流中来返回。

PojoRequestHandler

通过泛型的方式,您可以自定义输入和输出的类型,但是输入和输出的类型必须是 POJO 类型。

StreamRequestHandler
package example;
import com.aliyun.fc.runtime.Context;import com.aliyun.fc.runtime.StreamRequestHandler;
import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;
public class HelloFC implements StreamRequestHandler {
    @Override    public void handleRequest(            InputStream inputStream, OutputStream outputStream, Context context) throws IOException {        outputStream.write(new String("hello world").getBytes());    }}

包名和类名:由于 Java 有包的概念,因此执行方法和其他语言有所不同,需要带有包信息。代码例子中对应的执行方法为 example.Hello::mainHandler,此处 example 标识为 Java package,HelloFC 标识为类,mainHandler 标识为类方法。

包名和类名可以是任意的,但是需要与创建函数时的函数入口(handler)字段相对应。上述的例子包名是 example,类名是 HelloFC,那么创建函数时指定的 handler 为 example.HelloFC::handleRequest,handler 的格式为{package}.{class}::{method}。

实现的接口:您的代码中必须要实现函数计算预定义的接口。上述的代码示例中实现了 StreamRequestHandler,其中的 inputStream 参数是调用函数时传入的数据,outputStream 参数用于返回函数的执行结果。

context 参数中包含一些函数的运行时信息(例如 requestId 等),其类型是 com.aliyun.fc.runtime.Context。

返回值:实现 StreamRequestHandler 接口的函数通过 outputStream 参数返回执行结果。

引入接口库:其中用到的 com.aliyun.fc.runtime 这个包的依赖可以通过下文的 pom.xml 引用。

<dependency>    <groupId>com.aliyun.fc.runtime</groupId>    <artifactId>fc-java-core</artifactId>    <version>1.3.0</version></dependency>

在创建函数之前,您需要将代码及其依赖的 fc-java-core 打成 JAR 包。

PojoRequestHandler
// HelloFC.javapackage example;
import com.aliyun.fc.runtime.Context;import com.aliyun.fc.runtime.PojoRequestHandler;
public class HelloFC implements PojoRequestHandler<SimpleRequest, SimpleResponse> {
    @Override    public SimpleResponse handleRequest(SimpleRequest request, Context context) {        String message = "Hello, " + request.getFirstName() + " " + request.getLastName();        return new SimpleResponse(message);    }}
// SimpleRequest.javapackage example;
public class SimpleRequest {    String firstName;    String lastName;
    public String getFirstName() {        return firstName;    }
    public void setFirstName(String firstName) {        this.firstName = firstName;    }
    public String getLastName() {        return lastName;    }
    public void setLastName(String lastName) {        this.lastName = lastName;    }
    public SimpleRequest() {}    public SimpleRequest(String firstName, String lastName) {        this.firstName = firstName;        this.lastName = lastName;    }}
// SimpleResponse.javapackage example;
public class SimpleResponse {    String message;
    public String getMessage() {        return message;    }
    public void setMessage(String message) {        this.message = message;    }
    public SimpleResponse() {}    public SimpleResponse(String message) {        this.message = message;    }}

.net#

函数计算使用 C#编写函数,需要 Nuget 引入 Aliyun.Serverless.Core 包。

Stream handler
using System.IO;using System.Threading.Tasks;using Aliyun.Serverless.Core;using Microsoft.Extensions.Logging;
namespace FC.Examples{    public class TestHandler    {        public async Task<Stream> Echo(Stream input, IFcContext context)        {            ILogger logger = context.Logger;            logger.LogInformation("Handle request: {0}", context.RequestId);            MemoryStream copy = new MemoryStream();            await input.CopyToAsync(copy);            copy.Seek(0, SeekOrigin.Begin);            return copy;        }    }}

关于 POCO handler。除了 Stream 作为输入输出参数,POCO(Plain old CLR objects)对象同样也可以作为输入和输出。如果该 POCO 没有指定特定的 JSON Serializer 对象,则函数计算默认用 JSON.Net 进行对象的 JSON Serialize 和 Deserialize.

POCO handler
using Microsoft.Extensions.Logging;
namespace FC.Examples{    public class TestHandler    {        public class Product        {            public string Id { get; set; }            public string Description { get; set; }        }
        // optional serializer class, if it’s not specified, the default serializer (based on JSON.Net) will be used.        // [FcSerializer(typeof(MySerialization))]        public Product Echo(Product product, IFcContext context)        {            string Id = product.Id;            string Description = product.Description;            context.Logger.LogInformation("Id {0}, Description {1}", Id, Description);            return product;        }    }}

在上文 handler 例子中,如果 Assembly 文件为 test_assembly, 则其 handler 字符串是 test_assembly::FC.Examples.TestHandler::Echo。

有关限制如下:

handler 参数格式严格按照上述定义,也就是说参数 1 为必须输入,参数 2 可选,但必须为 IFcContext。 handler 函数不支持 Generic Method。 输入输出参数必须为 Stream 或可 JSON 序列化。 Async 函数返回值 Task 中 T 必须为 Stream 或可 JSON 序列化的类。