自定义请求体

最近更新时间:2023-09-19 15:49:31

我的收藏

操作场景

客户端发给业务后端的请求体中包含很多字段,如果您需要修改请求体内容,可以通过自定义请求体插件实现。
自定义请求体插件作用在请求过程中, 请求体改写服务可部署在云函数、公网、或内网 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

使用 Python 语言修改请求体的函数的编写方法请您参见 自定义请求体 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 header2
replace_headers.addProperty("header1", "header1-value");
replace_headers.addProperty("header2", "header2-value");

// 示例:删除 header3
remove_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 query2
replace_querys.addProperty("query1", "query1-value");
replace_querys.addProperty("query2", "query2-value");

// 示例:删除 header3
remove_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 解绑。

自定义服务类型规则

自定义插件的自定义服务类型,支持绑定不同实例上的服务 API,规则请参见 插件概述-已支持自定义插件类型

示例代码

自定义请求体时,HTTP 服务的示例代码如下:
Python 2.7
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
import json

PORT_NUMBER = 8022


def handle_header(header, response):
replace_headers = {} # 需要替换或者新增的header
remove_headers = [] # 需要删除的header

header_parameters = header # 客户端原始请求的header内容
if header_parameters is None or not isinstance(header_parameters, dict):
print("Invalid event.headerParameters")
return

# 示例:替换或新增 header1
replace_headers["header1"] = "header1"

# 示例:删除 header2
if 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")
return

response["replace_headers"] = replace_headers
response["remove_headers"] = remove_headers


def handle_body(body, response):
replace_body = "" # 替换之后的body内容
# 示例:替换body
if body is not None:
replace_body = "hello world"

# replace_body 必须为字符串类型
if not isinstance(replace_body, str):
print("Invalid custom request body")
return

response["replace_body"] = replace_body


def handle_query(query, response):
replace_query = {} # 需要替换或者新增的query
remove_query = [] # 需要删除的query

query_parameters = query
if query_parameters is None or not isinstance(query_parameters, dict):
print("Invalid event.queryStringParameters")
return

# 示例:替换或新增 query1
replace_query["query1"] = "query1"

# 示例:删除原始请求中的 query2
if 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")
return

response["replace_query"] = replace_query
response["remove_query"] = remove_query


class 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."

# 重写header
handle_header(self.headers.__dict__, response)

# 重写query
parsed_url = urlparse(self.path)
request_query = parse_qs(parsed_url.query)
handle_query(request_query, response)

# 重写body
handle_body(request_body, response)

self._set_headers()
self.wfile.write(json.dumps(response).encode('utf-8'))
return


try:
server = HTTPServer(('', PORT_NUMBER), MyHandler)
print(f'Started HTTP server on port {PORT_NUMBER}')

# Wait forever for incoming http requests
server.serve_forever()

except KeyboardInterrupt:
print('^C received, shutting down the web server')
server.socket.close()