操作场景
客户端发给业务后端的请求体中包含很多字段,如果您需要修改请求体内容,可以通过自定义请求体插件实现。
自定义请求体插件作用在请求过程中, 请求体改写服务可部署在云函数、公网、或内网 VPC 上。客户端请求 API 网关后,API 网关会将请求内容转发到请求体改写服务中,请求体内容修改完成后,将修改后的请求体响应给 API 网关,API 网关再将修改后的请求体转发给业务后端。
前提条件
说明:
自定义请求体插件目前仅支持事件函数,不支持 Web 函数。
操作步骤
步骤1:创建修改请求体函数
对于部署在公网或内网 VPC 的认证函数,可省略该步骤。
1. 登录 云函数控制台。
2. 在左侧导航栏,单击函数服务,进入函数列表页面。
3. 单击页面左上角的新建,新建一个函数,内容为修改请求体。
步骤2:创建自定义请求体插件
1. 登录 API 网关控制台。
2. 在左侧导航栏,单击插件 > 自定义插件,进入自定义插件列表页面。
3. 单击页面左上角的新建,新建一个自定义请求体插件。
对于部署在云函数的认证服务,创建自定义请求体插件时需要填写的数据如下:
参数 | 是否必填 | 说明 |
选择函数 | 必填 | 选择修改请求体的函数所在的命名空间、名称和版本。 |
后端超时 | 必填 | 设置 API 网关转发到修改请求体的函数的后端超时时间,超时时间的最大限制为30分钟。在 API 网关调用修改请求体的函数,未在超时时间内获得响应时,API 网关将终止此次调用,并返回相应的错误信息。 |
自定义内容 | 必填 | 设置 API 网关发送给修改请求体的函数的请求内容,支持选择 Header、Body、Query。未选择的请求内容部分将不被修改,直接转发给业务后端。 |
Base64 编码 | 必填 | 是否将请求内容 Base64 编码后再转发给修改请求体的函数,一般适用于请求内容是二进制的情况。 |
对于部署在公网的认证服务,创建自定义认证插件时需要填写的数据如下:
参数 | 是否必填 | 说明 |
请求方法 | 必填 | 请求自定义请求体函数的方法,支持 GET、POST、PUT、DELETE、HEAD、ANY。 |
公网服务 | 必填 | 请求体服务访问地址,支持 HTTP 和 HTTPS 协议。 |
路径匹配模式 | 必填 | 支持后端路径匹配和全路径匹配两种方式。 后端路径匹配:直接使用配置的路径请求服务。 全路径匹配:使用去除请求路径的路径请求服务,如 API 路径配置为 /a/,请求路径为 /a/b,开启全路径匹配后,传输给服务的为 /b。 |
对于部署在内网 VPC 的认证服务,创建自定义认证插件时需要填写的数据如下:
参数 | 是否必填 | 说明 |
选择 VPC | 必填 | 选择自定义请求改写服务所属的 VPC。 |
请求方法 | 必填 | 请求自定义请求改写服务的方法,支持 GET、POST、PUT、DELETE、HEAD、ANY。 |
后端地址 | 必填 | 自定义请求体服务访问地址,支持 HTTP 和 HTTPS 协议。 |
步骤3:绑定 API
1. 在列表中选中 步骤2 创建好的插件,单击操作列的绑定 API。
2. 在绑定 API 弹窗中选择服务和环境,并选择需要绑定插件的 API。
3. 单击确定,即可将插件绑定到 API,此时插件的配置已经对 API 生效。
自定义请求体云函数的编写方法
返回值定义
API 网关自定义请求体插件需要接受自定义请求体云函数返回特定格式的 Response,具体格式如下:
{"replace_headers":{"header1":"header1-value","header2":"header2-value"},"remove_headers":["header3","header4"],"replace_body":"hello","replace_querys":{"query1":"query1-value","query2":"query2-value"},"remove_querys":["query3","query4"]}
Python Demo
Java Demo
package com.example.demo;import com.google.gson.JsonArray;import com.google.gson.JsonObject;import com.qcloud.services.scf.runtime.events.APIGatewayProxyRequestEvent;public class Demo {public String mainHandler(APIGatewayProxyRequestEvent request) {System.out.println("helloworld");System.out.println(request.getHttpMethod());JsonObject resp = new JsonObject();headerHandler(request, resp);headerQuery(request, resp);headerBody(request, resp);return resp.toString();}private void headerHandler(APIGatewayProxyRequestEvent request, JsonObject resp) {JsonObject replace_headers = new JsonObject();JsonArray remove_headers = new JsonArray();// 示例:替换或新增 header1 header2replace_headers.addProperty("header1", "header1-value");replace_headers.addProperty("header2", "header2-value");// 示例:删除 header3remove_headers.add("header3");resp.add("replace_headers", replace_headers);resp.add("remove_headers", remove_headers);}private void headerQuery(APIGatewayProxyRequestEvent request, JsonObject resp) {JsonObject replace_querys = new JsonObject();JsonArray remove_querys = new JsonArray();// 示例:替换或新增 query1 query2replace_querys.addProperty("query1", "query1-value");replace_querys.addProperty("query2", "query2-value");// 示例:删除 header3remove_querys.add("query3");resp.add("replace_querys", replace_querys);resp.add("remove_querys", remove_querys);}private void headerBody(APIGatewayProxyRequestEvent request, JsonObject resp) {resp.addProperty("replace_body", "{'name':'Yagr'}");}}
PluginData
{"endpoint_timeout":15, // 后端超时时间,单位秒,合法值:0 ~ 60 秒"func_name":"test_name", // 自定义函数名称"func_namespace":"test_namespace", // 自定义函数命名空间"func_qualifier":"$LATEST", // 自定义函数版本"is_base64_encoded":true, // 是否将请求内容 Base64 编码后再转发给修改请求体的函数"is_send_req_body":true, // 是否将请求的Body内容发送到函数"is_send_req_headers":true, // 是否将请求的Header内容发送到函数"is_send_req_querys":true, // // 是否将请求的Query内容发送到函数"user_id":1253970226 // appid}
注意事项
每次将自定义插件绑定到一个网关 API 时,相当于为修改请求体的函数创建了一个该网关 API 的触发器。在 SCF 侧删除触发器,相当于把插件和 API 解绑。
自定义服务类型规则
示例代码
自定义请求体时,HTTP 服务的示例代码如下:
from http.server import BaseHTTPRequestHandler, HTTPServerfrom urllib.parse import urlparse, parse_qsimport jsonPORT_NUMBER = 8022def handle_header(header, response):replace_headers = {} # 需要替换或者新增的headerremove_headers = [] # 需要删除的headerheader_parameters = header # 客户端原始请求的header内容if header_parameters is None or not isinstance(header_parameters, dict):print("Invalid event.headerParameters")return# 示例:替换或新增 header1replace_headers["header1"] = "header1"# 示例:删除 header2if header_parameters.get("header2") is not None:remove_headers.append("header2")# replace_headers 和 remove_headers 至少有一个非空if len(replace_headers) < 1 and len(remove_headers) < 1:print("Invalid custom request headers")returnresponse["replace_headers"] = replace_headersresponse["remove_headers"] = remove_headersdef handle_body(body, response):replace_body = "" # 替换之后的body内容# 示例:替换bodyif body is not None:replace_body = "hello world"# replace_body 必须为字符串类型if not isinstance(replace_body, str):print("Invalid custom request body")returnresponse["replace_body"] = replace_bodydef handle_query(query, response):replace_query = {} # 需要替换或者新增的queryremove_query = [] # 需要删除的queryquery_parameters = queryif query_parameters is None or not isinstance(query_parameters, dict):print("Invalid event.queryStringParameters")return# 示例:替换或新增 query1replace_query["query1"] = "query1"# 示例:删除原始请求中的 query2if query_parameters.get("query2") is not None:remove_query.append("query2")# replace_query 和 remove_query 至少有一个非空if len(replace_query) < 1 and len(remove_query) < 1:print("Invalid custom request query")returnresponse["replace_query"] = replace_queryresponse["remove_query"] = remove_queryclass MyHandler(BaseHTTPRequestHandler):def _set_headers(self):self.send_response(200)self.send_header('Content-type', 'application/json')self.end_headers()def do_POST(self):response = {}try:request_body = self.rfile.read(int(self.headers.getheader("Content-length")))except:request_body = "no body."# 重写headerhandle_header(self.headers.__dict__, response)# 重写queryparsed_url = urlparse(self.path)request_query = parse_qs(parsed_url.query)handle_query(request_query, response)# 重写bodyhandle_body(request_body, response)self._set_headers()self.wfile.write(json.dumps(response).encode('utf-8'))returntry:server = HTTPServer(('', PORT_NUMBER), MyHandler)print(f'Started HTTP server on port {PORT_NUMBER}')# Wait forever for incoming http requestsserver.serve_forever()except KeyboardInterrupt:print('^C received, shutting down the web server')server.socket.close()