函数计算支持API网关作为事件源,即支持将函数计算设置为API的后端服务。当请求设置后端服务为函数计算时,API网关会触发关联的函数执行一次,函数计算将执行结果返回给API网关。本文介绍在函数计算控制台配置API网关触发函数执行的流程,包括配置函数的入口参数、编写函数代码并测试等。

前提条件

步骤一:配置函数的入口参数

API网关触发函数执行时,API网关的信息以event的形式作为输入参数传给函数,您可以将API网关传入的event信息作为参数,调试函数代码编写是否正确。

  1. 在函数详情页面,单击函数代码页签,然后单击xialatubiao图标,从下拉列表中,选择配置测试参数
  2. 配置测试参数面板,选择创建新测试事件编辑已有测试事件页签,填写事件名称和事件内容。然后单击确定
    event格式示例如下所示:
    {
        "path":"api request path",
        "httpMethod":"request method name",
        "headers":{all headers,including system headers},
        "queryParameters":{query parameters},
        "pathParameters":{path parameters},
        "body":"string of request payload",
        "isBase64Encoded":"true|false, indicate if the body is Base64-encode"
    }  
    说明
    • 如果isBase64Encoded的值为true,表示API网关传给函数计算的body内容已进行Base64编码。函数计算需要先对body内容进行Base64解码后再处理。
    • 如果isBase64Encoded的值为false,表示API网关没有对body内容进行Base64编码,在函数中可以直接获取body内容。

步骤二:编写函数代码并测试

  1. 登录函数计算控制台
  2. 在左侧导航栏,单击服务及函数
  3. 在顶部菜单栏,选择地域。
  4. 服务列表页面,找到目标服务,在其右侧操作列单击函数管理
  5. 函数管理页面,单击目标函数名称。
  6. 在函数详情页面,单击函数代码页签,在代码编辑器中编写代码,然后单击保存并部署
    不同语言的示例代码如下:
    • Node.js
      module.exports.handler = function(event, context, callback) { 
         var event = JSON.parse(event);
         var content = {
           path: event.path,
           method: event.method,
           headers: event.headers,
           queryParameters: event.queryParameters,
           pathParameters: event.pathParameters,
           body: event.body
         // 您可以在这里编写您自己的逻辑。  
         }
         var response = {
              isBase64Encoded: false,
              statusCode: '200',
              headers: {
                'x-custom-header': 'header value'
              },
              body: content
            }; 
         callback(null, response)
      };                               
    • Python
      # -*- coding: utf-8 -*-
      import json
      
      def handler(event, context):
          event = json.loads(event)
          content = {
              'path': event['path'],
              'method': event['httpMethod'],
              'headers': event['headers'],
              'queryParameters': event['queryParameters'],
              'pathParameters': event['pathParameters'],
              'body': event['body']
          }
          # 您可以在这里编写您自己的逻辑。 
          rep = {
              "isBase64Encoded": "false",
              "statusCode": "200",
              "headers": {
                  "x-custom-header": "no"
              },
              "body": content
          }
          return json.dumps(rep)                                
    • PHP
      <?php
      function handler($event, $context) {
          $event   = json_decode($event, $assoc = true);
          $content = [
              'path'            => $event['path'],
              'method'          => $event['httpMethod'],
              'headers'         => $event['headers'],
              'queryParameters' => $event['queryParameters'],
              'pathParameters'  => $event['pathParameters'],
              'body'            => $event['body'],
          ];
      
          $rep = [
              "isBase64Encoded" => "false",
              "statusCode"      => "200",
              "headers"         => [
                  "x-custom-header" => "no",
              ],
              "body"            => $content,
          ];
      
          return json_encode($rep);
      }                               
    • Java

      使用Java编程时,必须要实现一个类,需要实现函数计算预定义的handler,目前有两个预定义的handler可以实现(任选其一即可)。函数计算Java运行环境,请参见编译部署代码包

      • (推荐)使用PojoRequestHandler<I, O> handler。
        import com.aliyun.fc.runtime.Context;
        import com.aliyun.fc.runtime.PojoRequestHandler;
        import java.util.HashMap;
        import java.util.Map;
        
        public class ApiTriggerDemo implements PojoRequestHandler<ApiRequest, ApiResponse> {
        
            public ApiResponse handleRequest(ApiRequest request, Context context) {
                // 获取API请求信息。
                context.getLogger().info(request.toString());
                String path = request.getPath();
                String httpMethod = request.getHttpMethod();
                String body = request.getBody();
                context.getLogger().info("path:" + path);
                context.getLogger().info("httpMethod:" + httpMethod);
                context.getLogger().info("body:" + body);
        
                // 您可以在这里编写您自己的逻辑。
        
                // API返回示例。
                Map headers = new HashMap();
                boolean isBase64Encoded = false;
                int statusCode = 200;
                String returnBody = "";
                return new ApiResponse(headers,isBase64Encoded,statusCode,returnBody);
            }
        }                                        
        • 两个POJO类、ApiRequest类和ApiResponse类定义如下。
          说明 POJO类的set()get()方法要写全。
          import java.util.Map;
          
          public class ApiRequest {
              private String path;
              private String httpMethod;
              private Map headers;
              private Map queryParameters;
              private Map pathParameters;
              private String body;
              private boolean isBase64Encoded;
          
              @Override
              public String toString() {
                  return "Request{" +
                          "path='" + path + '\'' +
                          ", httpMethod='" + httpMethod + '\'' +
                          ", headers=" + headers +
                          ", queryParameters=" + queryParameters +
                          ", pathParameters=" + pathParameters +
                          ", body='" + body + '\'' +
                          ", isBase64Encoded=" + isBase64Encoded +
                          '}';
              }
          
              public String getPath() {
                  return path;
              }
          
              public void setPath(String path) {
                  this.path = path;
              }
          
              public String getHttpMethod() {
                  return httpMethod;
              }
          
              public void setHttpMethod(String httpMethod) {
                  this.httpMethod = httpMethod;
              }
          
              public Map getHeaders() {
                  return headers;
              }
          
              public void setHeaders(Map headers) {
                  this.headers = headers;
              }
          
              public Map getQueryParameters() {
                  return queryParameters;
              }
          
              public void setQueryParameters(Map queryParameters) {
                  this.queryParameters = queryParameters;
              }
          
              public Map getPathParameters() {
                  return pathParameters;
              }
          
              public void setPathParameters(Map pathParameters) {
                  this.pathParameters = pathParameters;
              }
          
              public String getBody() {
                  return body;
              }
          
              public void setBody(String body) {
                  this.body = body;
              }
          
              public boolean getIsBase64Encoded() {
                  return this.isBase64Encoded;
              }
          
              public void setIsBase64Encoded(boolean base64Encoded) {
                  this.isBase64Encoded = base64Encoded;
              }
          }                                      
          import java.util.Map;
          
          public class ApiResponse {
              private Map headers;
              private boolean isBase64Encoded;
              private int statusCode;
              private String body;
          
              public ApiResponse(Map headers, boolean isBase64Encoded, int statusCode, String body) {
                  this.headers = headers;
                  this.isBase64Encoded = isBase64Encoded;
                  this.statusCode = statusCode;
                  this.body = body;
              }
          
              public Map getHeaders() {
                  return headers;
              }
          
              public void setHeaders(Map headers) {
                  this.headers = headers;
              }
          
              public boolean getIsBase64Encoded() {
                  return isBase64Encoded;
              }
          
              public void setIsBase64Encoded(boolean base64Encoded) {
                  this.isBase64Encoded = base64Encoded;
              }
          
              public int getStatusCode() {
                  return statusCode;
              }
          
              public void setStatusCode(int statusCode) {
                  this.statusCode = statusCode;
              }
          
              public String getBody() {
                  return body;
              }
          
              public void setBody(String body) {
                  this.body = body;
              }
          }                                       
        • pom.xml文件如下。
          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
              <modelVersion>4.0.0</modelVersion>
          
              <groupId>apiTrigger</groupId>
              <artifactId>apiTrigger</artifactId>
              <version>1.0-SNAPSHOT</version>
              <build>
                  <plugins>
                      <plugin>
                          <groupId>org.apache.maven.plugins</groupId>
                          <artifactId>maven-compiler-plugin</artifactId>
                          <configuration>
                              <source>1.8</source>
                              <target>1.8</target>
                          </configuration>
                      </plugin>
                  </plugins>
              </build>
              <dependencies>
                  <dependency>
                      <groupId>com.aliyun.fc.runtime</groupId>
                      <artifactId>fc-java-core</artifactId>
                      <version>1.0.0</version>
                  </dependency>
              </dependencies>   
          </project>                                        
      • 使用StreamRequestHandler handler。

        使用该handler,需要将输入的InputStream转换为对应的POJO类,示例代码如下。

        pom.xml文件配置与使用PojoRequestHandler<I, O> handler相同。

        import com.aliyun.fc.runtime.Context;
        import com.aliyun.fc.runtime.StreamRequestHandler;
        import com.aliyun.fc.runtime.Context;
        import com.google.gson.Gson;
        import java.io.*;
        import java.util.Base64;
        import java.util.HashMap;
        import java.util.Map;
        
        public class ApiTriggerDemo2 implements StreamRequestHandler {
        
            public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) {
                try {
                    // 将InputStream转化成字符串。
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                    StringBuffer stringBuffer = new StringBuffer();
                    String string = "";
                    while ((string = bufferedReader.readLine()) != null) {
                        stringBuffer.append(string);
                    }
                    String input = stringBuffer.toString();
                    context.getLogger().info("inputStream: " + input);
                    Request req = new Gson().fromJson(input, Request.class);
                    context.getLogger().info("input req: ");
                    context.getLogger().info(req.toString());
                    String bodyReq = req.getBody();
                    Base64.Decoder decoder = Base64.getDecoder();
                    context.getLogger().info("body: " + new String(decoder.decode(bodyReq)));
        
                    // 您可以在这里处理您自己的逻辑。
        
                    // 返回结构。
                    Map headers = new HashMap();
                    headers.put("x-custom-header", " ");
                    boolean isBase64Encoded = false;
                    int statusCode = 200;
                    Map body = new HashMap();
                    Response resp = new Response(headers, isBase64Encoded, statusCode, body);
                    String respJson = new Gson().toJson(resp);
                    context.getLogger().info("outputStream: " + respJson);
                    outputStream.write(respJson.getBytes());
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        outputStream.close();
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            class Request {
                private String path;
                private String httpMethod;
                private Map headers;
                private Map queryParameters;
                private Map pathParameters;
                private String body;
                private boolean isBase64Encoded;
        
                @Override
                public String toString() {
                    return "Request{" +
                            "path='" + path + '\'' +
                            ", httpMethod='" + httpMethod + '\'' +
                            ", headers=" + headers +
                            ", queryParameters=" + queryParameters +
                            ", pathParameters=" + pathParameters +
                            ", body='" + body + '\'' +
                            ", isBase64Encoded=" + isBase64Encoded +
                            '}';
                }
        
                public String getBody() {
                    return body;
                }
            }
        
            // 函数计算需要以以下JSON格式返回对API网关的响应。
            class Response {
                private Map headers;
                private boolean isBase64Encoded;
                private int statusCode;
                private Map body;
        
                public Response(Map headers, boolean isBase64Encoded, int statusCode, Map body) {
                    this.headers = headers;
                    this.isBase64Encoded = isBase64Encoded;
                    this.statusCode = statusCode;
                    this.body = body;
                }
            }
        }                                        
  7. 单击函数代码页签的测试函数

步骤三:验证结果

执行完成后,您可以在函数代码页签的上方查看执行结果。

函数计算需要将执行的结果按照以下JSON格式返回给API网关,然后由API网关解析。返回格式示例如下:
{
    "isBase64Encoded":true|false,
    "statusCode":httpStatusCode,
    "headers":{response headers},
    "body":"..."
}       
说明
  • 如果函数计算返回给API网关的结果不符合格式要求,API网关会返回503 Service Unavailable。
  • body内容为二进制时,需对其进行Base64编码,即isBase64Encoded设置为true;当body内容为非二进制时,无需对其进行Base64编码,即isBase64Encoded设置为false

常见问题

API网关触发函数执行时报503,查看函数日志,函数已经执行成功了,这是怎么回事?

API网关和函数计算的对接有格式要求,如果函数计算返回给API网关的结果没有按规定的格式返回,那么API网关就认为后端服务不可用。关于API网关和函数计算的对接格式要求,请参见触发器event格式函数计算的返回参数格式

如何设置返回响应的content-type?

如图所示,在API设置的时候可以设置响应的content-type。详细内容,请参见创建后端为函数计算的APIcontent-type

API网关触发函数计算执行,已经调通的函数,一段时间不调用,再次调用会报503,这是什么原因?

一段时间不调用后,函数重新调用需要准备执行环境,有冷启动时延,在API网关设置的超时时间内没有调用完,API网关会认为后端服务不可用。延长API网关的超时时间即可解决问题。

为什么函数中接收到API网关传过来的body是经过了Base64编码的?

API网关对FORM形式的body传输是不进行Base64编码的(使用FORM形式需要在API网关选择入参映射),其他形式body都会进行Base64编码,避免内容传输错误或者丢失。建议您在使用时,先判断event中isBase64是否为true。如果isBase64为true,则body需要在函数中进行解码。关于API网关传给函数计算的event格式,请参见触发器event格式

更多信息

除了函数计算控制台,您还可通过以下方式配置触发器:
  • 通过Serverless Devs工具配置触发器。更多操作,请参见Serverless Devs
  • 通过SDK配置触发器。更多操作,请参见SDK列表

如需对创建的触发器进行修改或删除,请参见触发器管理