body-transformer
body-transformer 插件执行基于模板的转换,将请求和/或响应体从一种格式转换为另一种格式。
示例
以下示例演示了如何在不同场景下配置 body-transformer。
转换模板使用 lua-resty-template 语法。有关详细信息,请参阅模板语法。
你还可以使用辅助函数 _escape_json() 和 _escape_xml() 来转义双引号等特殊字符,使用 _body 访问请求体,使用 _ctx 访问上下文变量。
在所有情况下,你应确保转换模板是一个有效的 JSON 字符串。
在 JSON 和 XML SOAP 之间转换
以下示例演示了如何在与 SOAP 上游服务一起工作时,将请求体从 JSON 转换为 XML,并将响应体从 XML 转换为 JSON。
启动示例 SOAP 服务:
cd /tmp
git clone https://github.com/spring-guides/gs-producing-web-service.git
cd gs-producing-web-service/complete
./mvnw spring-boot:run
创建请求和响应转换模板:
req_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr -d '\r\n'
<?xml version="1.0"?>
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:Body>
<ns0:getCountryRequest xmlns:ns0="https://spring.io/guides/gs-producing-web-service">
<ns0:name>{{_escape_xml(name)}}</ns0:name>
</ns0:getCountryRequest>
</soap-env:Body>
</soap-env:Envelope>
EOF
)
rsp_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr -d '\r\n'
{% if Envelope.Body.Fault == nil then %}
{
"status":"{{_ctx.var.status}}",
"currency":"{{Envelope.Body.getCountryResponse.country.currency}}",
"population":{{Envelope.Body.getCountryResponse.country.population}},
"capital":"{{Envelope.Body.getCountryResponse.country.capital}}",
"name":"{{Envelope.Body.getCountryResponse.country.name}}"
}
{% else %}
{
"message":{*_escape_json(Envelope.Body.Fault.faultstring[1])*},
"code":"{{Envelope.Body.Fault.faultcode}}"
{% if Envelope.Body.Fault.faultactor ~= nil then %}
, "actor":"{{Envelope.Body.Fault.faultactor}}"
{% end %}
}
{% end %}
EOF
)
上面使用了 awk 和 tr 来操作模板,以确保模板是一个有效的 JSON 字符串。
使用之前创建的模板创建一个带有 body-transformer 的路由:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "body-transformer-route",
"methods": ["POST"],
"uri": "/services",
"plugins": {
"body-transformer": {
"request": {
"template": "'"$req_template"'",
"input_format": "json"
},
"response": {
"template": "'"$rsp_template"'",
"input_format": "xml"
}
},
"proxy-rewrite": {
"headers": {
"set": {
"Content-Type": "text/xml"
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"host.docker.internal:8080": 1
}
}
}'
使用之前创建的模板创建一个带有 body-transformer 的路由:
services:
- name: body-transformer-service
routes:
- name: body-transformer-route
methods:
- POST
uris:
- /services
plugins:
body-transformer:
request:
template: |
<?xml version="1.0"?>
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:Body>
<ns0:getCountryRequest xmlns:ns0="https://spring.io/guides/gs-producing-web-service">
<ns0:name>{{_escape_xml(name)}}</ns0:name>
</ns0:getCountryRequest>
</soap-env:Body>
</soap-env:Envelope>
input_format: json
response:
template: |
{% if Envelope.Body.Fault == nil then %}
{
"status":"{{_ctx.var.status}}",
"currency":"{{Envelope.Body.getCountryResponse.country.currency}}",
"population":{{Envelope.Body.getCountryResponse.country.population}},
"capital":"{{Envelope.Body.getCountryResponse.country.capital}}",
"name":"{{Envelope.Body.getCountryResponse.country.name}}"
}
{% else %}
{
"message":{*_escape_json(Envelope.Body.Fault.faultstring[1])*},
"code":"{{Envelope.Body.Fault.faultcode}}"
{% if Envelope.Body.Fault.faultactor ~= nil then %}
, "actor":"{{Envelope.Body.Fault.faultactor}}"
{% end %}
}
{% end %}
input_format: xml
proxy-rewrite:
headers:
set:
Content-Type: text/xml
upstream:
type: roundrobin
nodes:
- host: host.docker.internal
port: 8080
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
创建一个启用了 body-transformer 插件的 路由 Kubernetes 清单文件:
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: json-xml-plugin
spec:
plugins:
- name: body-transformer
config:
request:
template: |
<?xml version="1.0"?>
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:Body>
<ns0:getCountryRequest xmlns:ns0="https://spring.io/guides/gs-producing-web-service">
<ns0:name>{{_escape_xml(name)}}</ns0:name>
</ns0:getCountryRequest>
</soap-env:Body>
</soap-env:Envelope>
input_format: json
response:
template: |
{% if Envelope.Body.Fault == nil then %}
{
"status":"{{_ctx.var.status}}",
"currency":"{{Envelope.Body.getCountryResponse.country.currency}}",
"population":{{Envelope.Body.getCountryResponse.country.population}},
"capital":"{{Envelope.Body.getCountryResponse.country.capital}}",
"name":"{{Envelope.Body.getCountryResponse.country.name}}"
}
{% else %}
{
"message":{*_escape_json(Envelope.Body.Fault.faultstring[1])*},
"code":"{{Envelope.Body.Fault.faultcode}}"
{% if Envelope.Body.Fault.faultactor ~= nil then %}
, "actor":"{{Envelope.Body.Fault.faultactor}}"
{% end %}
}
{% end %}
input_format: xml
- name: proxy-rewrite
config:
headers:
set:
Content-Type: text/xml
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: ws-external-domain
spec:
type: ExternalName
externalName: host.docker.internal
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- method: POST
path:
type: Exact
value: /services
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: json-xml-plugin
backendRefs:
- name: ws-external-domain
port: 8080
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
ingressClassName: apisix
http:
- name: body-transformer-route
match:
paths:
- /services
methods:
- POST
plugins:
- name: body-transformer
enable: true
config:
request:
template: |
<?xml version="1.0"?>
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:Body>
<ns0:getCountryRequest xmlns:ns0="https://spring.io/guides/gs-producing-web-service">
<ns0:name>{{_escape_xml(name)}}</ns0:name>
</ns0:getCountryRequest>
</soap-env:Body>
</soap-env:Envelope>
input_format: json
response:
template: |
{% if Envelope.Body.Fault == nil then %}
{
"status":"{{_ctx.var.status}}",
"currency":"{{Envelope.Body.getCountryResponse.country.currency}}",
"population":{{Envelope.Body.getCountryResponse.country.population}},
"capital":"{{Envelope.Body.getCountryResponse.country.capital}}",
"name":"{{Envelope.Body.getCountryResponse.country.name}}"
}
{% else %}
{
"message":{*_escape_json(Envelope.Body.Fault.faultstring[1])*},
"code":"{{Envelope.Body.Fault.faultcode}}"
{% if Envelope.Body.Fault.faultactor ~= nil then %}
, "actor":"{{Envelope.Body.Fault.faultactor}}"
{% end %}
}
{% end %}
input_format: xml
- name: proxy-rewrite
enable: true
config:
headers:
set:
Content-Type: text/xml
upstreams:
- name: ws-external-domain
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: ws-external-domain
spec:
externalNodes:
- type: Domain
name: host.docker.internal
port: 8080
将配置应用到集群:
kubectl apply -f soap-route.yaml
❶ 将请求输入格式设置为 JSON,以便插件在内部应用 JSON 解码器。
❷ 将响应输入格式设置为 XML,以便插件在内部应用 XML 解码器。
❸ 将 Content-Type 头设置为 text/xml,以便上游服务正确响应。
❹ SOAP 服务的地址。APISIX 在 Docker 中运行时,host.docker.internal 解析到宿主机;如果采用其他方式运行 APISIX,请替换为实际地址。
如果调整复杂的文本文件使其成为有效的转换模板比较麻烦,你可以使用 base64 工具对文件进行编码,如下所示:
"body-transformer": {
"request": {
"template": "'"$(base64 -w0 /path/to/request_template_file)"'"
},
"response": {
"template": "'"$(base64 -w0 /path/to/response_template_file)"'"
}
}
发送带有有效 JSON 体的请求:
curl "http://127.0.0.1:9080/services" -X POST -d '{"name": "Spain"}'
请求中发送的 JSON 体将在转发到上游 SOAP 服务之前转换为 XML,响应体将从 XML 转换回 JSON。
你应该看到类似于以下的响应:
{
"status": "200",
"currency": "EUR",
"population": 46704314,
"capital": "Madrid",
"name": "Spain"
}
修改请求体
以下示例演示了如何动态修改请求体。
- Admin API
- ADC
- Ingress Controller
创建一个带有 body-transformer 的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"template": "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
创建一个带有 body-transformer 的路由:
services:
- name: body-transformer-service
routes:
- name: body-transformer-route
uris:
- /anything
plugins:
body-transformer:
request:
template: "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}"
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: body-transformer-plugin-config
spec:
plugins:
- name: body-transformer
config:
request:
template: "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: body-transformer-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
ingressClassName: apisix
http:
- name: body-transformer-route
match:
paths:
- /anything
upstreams:
- name: httpbin-external-domain
plugins:
- name: body-transformer
enable: true
config:
request:
template: "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}"
将配置应用到集群:
kubectl apply -f body-transformer-ic.yaml
❶ 设置一个模板,将 "world" 追加到 name,并将 10 加到 age,然后将它们分别设置为 "foo" 和 "bar" 的值。
发送请求到路由:
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-d '{"name":"hello","age":20}' \
-i
你应该看到以下响应:
{
"args": {},
"data": "{\"foo\":\"hello world\",\"bar\":30}",
...
"json": {
"bar": 30,
"foo": "hello world"
},
"method": "POST",
...
}
使用变量生成请求体
以下示例演示了如何使用 ctx 上下文变量动态生成请求体。
- Admin API
- ADC
- Ingress Controller
创建一个带有 body-transformer 的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"template": "{\"foo\":\"{{_ctx.var.arg_name .. \" world\"}}\"}"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
创建一个带有 body-transformer 的路由:
services:
- name: body-transformer-service
routes:
- name: body-transformer-route
uris:
- /anything
plugins:
body-transformer:
request:
template: "{\"foo\":\"{{_ctx.var.arg_name .. \" world\"}}\"}"
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: body-transformer-plugin-config
spec:
plugins:
- name: body-transformer
config:
request:
template: "{\"foo\":\"{{_ctx.var.arg_name .. \" world\"}}\"}"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: body-transformer-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
ingressClassName: apisix
http:
- name: body-transformer-route
match:
paths:
- /anything
upstreams:
- name: httpbin-external-domain
plugins:
- name: body-transformer
enable: true
config:
request:
template: "{\"foo\":\"{{_ctx.var.arg_name .. \" world\"}}\"}"
将配置应用到集群:
kubectl apply -f body-transformer-ic.yaml
❶ 设置一个模板,使用 NGINX 变量 arg_name 访问请求参数。
发送带有 name 参数的请求到路由:
curl -i "http://127.0.0.1:9080/anything?name=hello"
你应该看到像这样的响应:
{
"args": {
"name": "hello"
},
...,
"json": {
"foo": "hello world"
},
...
}
将请求体从 YAML 转换为 JSON
以下示例演示了如何将请求体从 YAML 转换为 JSON。
创建请求转换模板:
req_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1'
{%
local yaml = require("tinyyaml")
local body = yaml.parse(_body)
%}
{"foobar":"{{body.foobar.foo .. " " .. body.foobar.bar}}"}
EOF
)
- Admin API
- ADC
- Ingress Controller
创建一个带有使用该模板的 body-transformer 的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"template": "'"$req_template"'"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
创建一个带有使用该模板的 body-transformer 的路由:
services:
- name: body-transformer-service
routes:
- name: body-transformer-route
uris:
- /anything
plugins:
body-transformer:
request:
template: |
{%
local yaml = require("tinyyaml")
local body = yaml.parse(_body)
%}
{"foobar":"{{body.foobar.foo .. " " .. body.foobar.bar}}"}
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: body-transformer-plugin-config
spec:
plugins:
- name: body-transformer
config:
request:
template: |
{%
local yaml = require("tinyyaml")
local body = yaml.parse(_body)
%}
{"foobar":"{{body.foobar.foo .. " " .. body.foobar.bar}}"}
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: body-transformer-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
ingressClassName: apisix
http:
- name: body-transformer-route
match:
paths:
- /anything
upstreams:
- name: httpbin-external-domain
plugins:
- name: body-transformer
enable: true
config:
request:
template: |
{%
local yaml = require("tinyyaml")
local body = yaml.parse(_body)
%}
{"foobar":"{{body.foobar.foo .. " " .. body.foobar.bar}}"}
将配置应用到集群:
kubectl apply -f body-transformer-ic.yaml
发送带有 YAML 体的请求到路由:
body='
foobar:
foo: hello
bar: world'
curl "http://127.0.0.1:9080/anything" -X POST \
-d "$body" \
-H "Content-Type: text/yaml" \
-i
你应该看到类似于以下的响应,验证了 YAML 体已被适当地转换为 JSON:
{
"args": {},
"data": "{\"foobar\":\"hello world\"}",
...
"json": {
"foobar": "hello world"
},
...
}
将表单 URL 编码请求体转换为 JSON
以下示例演示了如何将 form-urlencoded 体转换为 JSON。
- Admin API
- ADC
- Ingress Controller
创建一个带有 body-transformer 的路由如下:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"input_format": "encoded",
"template": "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
创建一个带有 body-transformer 的路由如下:
services:
- name: body-transformer-service
routes:
- name: body-transformer-route
uris:
- /anything
plugins:
body-transformer:
request:
input_format: encoded
template: "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}"
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: body-transformer-plugin-config
spec:
plugins:
- name: body-transformer
config:
request:
input_format: encoded
template: "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: body-transformer-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
ingressClassName: apisix
http:
- name: body-transformer-route
match:
paths:
- /anything
upstreams:
- name: httpbin-external-domain
plugins:
- name: body-transformer
enable: true
config:
request:
input_format: encoded
template: "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}"
将配置应用到集群:
kubectl apply -f body-transformer-ic.yaml
❶ 将 input_format 设置为 encoded。
❷ 设置一个模板,将字符串 world 追加到 name 输入,将 10 加到 age 输入,并形成一个新的 JSON 对象。
发送一个带有 encoded 体的 POST 请求到路由:
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'name=hello&age=20'
你应该看到类似于以下的响应:
{
"args": {},
"data": "",
"files": {},
"form": {
"{\"foo\":\"hello world\",\"bar\":30}": ""
},
"headers": {
...
},
...
}
将 GET 请求查询参数转换为请求体
以下示例演示了如何将 GET 请求查询参数转换为请求体。请注意,这不会转换 HTTP 方法。要转换方法,请参阅 proxy-rewrite。
- Admin API
- ADC
- Ingress Controller
创建一个带有 body-transformer 的路由如下:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"input_format": "args",
"template": "{\"message\": \"hello {{name}}\"}"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
创建一个带有 body-transformer 的路由如下:
services:
- name: body-transformer-service
routes:
- name: body-transformer-route
uris:
- /anything
plugins:
body-transformer:
request:
input_format: args
template: '{"message": "hello {{name}}"}'
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: body-transformer-plugin-config
spec:
plugins:
- name: body-transformer
config:
request:
input_format: args
template: '{"message": "hello {{name}}"}'
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: body-transformer-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
ingressClassName: apisix
http:
- name: body-transformer-route
match:
paths:
- /anything
upstreams:
- name: httpbin-external-domain
plugins:
- name: body-transformer
enable: true
config:
request:
input_format: args
template: '{"message": "hello {{name}}"}'
将配置应用到集群:
kubectl apply -f body-transformer-ic.yaml
❶ 将 input_format 设置为 args。
❷ 设置一个向请求添加消息的模板。
发送 GET 请求到路由:
curl "http://127.0.0.1:9080/anything?name=john"
你应该看到类似于以下的响应:
{
"args": {},
"data": "{\"message\": \"hello john\"}",
"files": {},
"form": {},
"headers": {
...
},
"json": {
"message": "hello john"
},
"method": "GET",
...
}
转换纯文本媒体类型
以下示例演示了如何转换带有 plain 媒体类型的请求。
- Admin API
- ADC
- Ingress Controller
创建一个带有 body-transformer 的路由如下:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"input_format": "plain",
"template": "{\"message\": \"{* string.gsub(_body, \"not \", \"\") *}\"}"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
创建一个带有 body-transformer 的路由如下:
services:
- name: body-transformer-service
routes:
- name: body-transformer-route
uris:
- /anything
plugins:
body-transformer:
request:
input_format: plain
template: "{\"message\": \"{* string.gsub(_body, \"not \", \"\") *}\"}"
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: body-transformer-plugin-config
spec:
plugins:
- name: body-transformer
config:
request:
input_format: plain
template: "{\"message\": \"{* string.gsub(_body, \"not \", \"\") *}\"}"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: body-transformer-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
ingressClassName: apisix
http:
- name: body-transformer-route
match:
paths:
- /anything
upstreams:
- name: httpbin-external-domain
plugins:
- name: body-transformer
enable: true
config:
request:
input_format: plain
template: "{\"message\": \"{* string.gsub(_body, \"not \", \"\") *}\"}"
将配置应用到集群:
kubectl apply -f body-transformer-ic.yaml
❶ 将 input_format 设置为 plain。
❷ 设置一个模板,从体字符串中删除 not 和随后的空格。
发送一个 POST 请求到路由:
curl "http://127.0.0.1:9080/anything" -X POST \
-d 'not actually json' \
-i
你应该看到类似于以下的响应:
{
"args": {},
"data": "",
"files": {},
"form": {
"{\"message\": \"actually json\"}": ""
},
"headers": {
...
},
...
}
转换 Multipart 媒体类型
以下示例演示了如何转换带有 multipart 媒体类型的请求。
创建一个请求转换模板,根据请求体中提供的 age 向 body 添加 status:
req_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1'
{%
if tonumber(context.age) > 18 then
context._multipart:set_simple("status", "adult")
else
context._multipart:set_simple("status", "minor")
end
local body = context._multipart:tostring()
%}{* body *}
EOF
)
- Admin API
- ADC
- Ingress Controller
创建一个带有 body-transformer 的路由如下:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"input_format": "multipart",
"template": "'"$req_template"'"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
创建一个带有 body-transformer 的路由如下:
services:
- name: body-transformer-service
routes:
- name: body-transformer-route
uris:
- /anything
plugins:
body-transformer:
request:
input_format: multipart
template: |
{%
if tonumber(context.age) > 18 then
context._multipart:set_simple("status", "adult")
else
context._multipart:set_simple("status", "minor")
end
local body = context._multipart:tostring()
%}{* body *}
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: body-transformer-plugin-config
spec:
plugins:
- name: body-transformer
config:
request:
input_format: multipart
template: |
{%
if tonumber(context.age) > 18 then
context._multipart:set_simple("status", "adult")
else
context._multipart:set_simple("status", "minor")
end
local body = context._multipart:tostring()
%}{* body *}
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: body-transformer-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
创建一个启用了 body-transformer 插件的路由 Kubernetes 清单文件:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
ingressClassName: apisix
http:
- name: body-transformer-route
match:
paths:
- /anything
upstreams:
- name: httpbin-external-domain
plugins:
- name: body-transformer
enable: true
config:
request:
input_format: multipart
template: |
{%
if tonumber(context.age) > 18 then
context._multipart:set_simple("status", "adult")
else
context._multipart:set_simple("status", "minor")
end
local body = context._multipart:tostring()
%}{* body *}
将配置应用到集群:
kubectl apply -f body-transformer-ic.yaml
❶ 将 input_format 设置为 multipart。
❷ 设置为之前创建的请求模板。
发送一个 multipart POST 请求到路由:
curl -X POST \
-F "name=john" \
-F "age=10" \
"http://127.0.0.1:9080/anything"
你应该看到类似于以下的响应:
{
"args": {},
"data": "",
"files": {},
"form": {
"age": "10",
"name": "john",
"status": "minor"
},
"headers": {
"Accept": "*/*",
"Content-Length": "361",
"Content-Type": "multipart/form-data; boundary=------------------------qtPjk4c8ZjmGOXNKzhqnOP",
...
},
...
}
基于消费者身份转换响应体
以下示例演示了如何根据不同的消费者身份自定义响应体转换。该示例展示了如何 向不同的消费者返回不同的响应格式,同时过滤敏感字段并重命名属性。
创建响应转换模板,该模板根据消费者身份应用不同的转换:
rsp_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr -d '\r\n'
{% local consumer_name = _ctx.consumer and _ctx.consumer.username or "" %}
{% if consumer_name == "consumer-a" then %}
{
"user_id": {* user_id *},
"display_name": {* _escape_json(username) *},
"email": {* _escape_json(email) *}
}
{% elseif consumer_name == "consumer-b" then %}
{
"user_id": {* user_id *},
"email": {* _escape_json(email) *},
"balance": {* balance *}
}
{% else %}
{* _body *}
{% end %}
EOF
)
- Admin API
- ADC
- Ingress Controller
创建三个配置了 key-auth 的消费者:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "consumer-a",
"plugins": {
"key-auth": {
"key": "consumer-a"
}
}
}'
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "consumer-b",
"plugins": {
"key-auth": {
"key": "consumer-b"
}
}
}'
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "consumer-c",
"plugins": {
"key-auth": {
"key": "consumer-c"
}
}
}'
创建一个带有 body-transformer、key-auth 和 mocking 插件的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "body-transformer-route",
"uri": "/mock",
"plugins": {
"key-auth": {},
"mocking": {
"response_example": "{\"user_id\":1001,\"username\":\"john_doe\",\"email\":\"john@example.com\",\"phone\":\"+1-555-0123\",\"balance\":1250.50}"
},
"body-transformer": {
"response": {
"input_format": "json",
"template": "'"$rsp_template"'"
}
}
}
}'
创建三个配置了 key-auth 的消费者,并创建一个启用了 body-transformer、key-auth 和 mocking 插件的路由:
consumers:
- username: consumer-a
credentials:
- name: key-auth
type: key-auth
config:
key: consumer-a
- username: consumer-b
credentials:
- name: key-auth
type: key-auth
config:
key: consumer-b
- username: consumer-c
credentials:
- name: key-auth
type: key-auth
config:
key: consumer-c
services:
- name: body-transformer-service
routes:
- name: body-transformer-route
uris:
- /mock
plugins:
key-auth: {}
mocking:
response_example: '{"user_id":1001,"username":"john_doe","email":"john@example.com","phone":"+1-555-0123","balance":1250.50}'
body-transformer:
response:
input_format: json
template: |
{% local consumer_name = _ctx.consumer and _ctx.consumer.username or "" %}
{% if consumer_name == "consumer-a" then %}
{
"user_id": {* user_id *},
"display_name": {* _escape_json(username) *},
"email": {* _escape_json(email) *}
}
{% elseif consumer_name == "consumer-b" then %}
{
"user_id": {* user_id *},
"email": {* _escape_json(email) *},
"balance": {* balance *}
}
{% else %}
{* _body *}
{% end %}
将配置同步到网关:
adc sync -f adc.yaml
创建三个配置了 key-auth 的消费者,并创建一个启用了 body-transformer、key-auth 和 mocking 插件的路由:
使用 Ingress Controller 配置消费者时,消费者名称会生成为 namespace_consumername 格式。因此,模板中的 consumer_name 逻辑应匹配这种格式的消费者名称。
- Gateway API
- APISIX CRD
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: consumer-a
spec:
gatewayRef:
name: apisix
credentials:
- type: key-auth
name: primary-key
config:
key: consumer-a
---
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: consumer-b
spec:
gatewayRef:
name: apisix
credentials:
- type: key-auth
name: primary-key
config:
key: consumer-b
---
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: consumer-c
spec:
gatewayRef:
name: apisix
credentials:
- type: key-auth
name: primary-key
config:
key: consumer-c
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: body-transformer-plugin-config
spec:
plugins:
- name: key-auth
config:
_meta:
disable: false
- name: mocking
config:
response_example: '{"user_id":1001,"username":"john_doe","email":"john@example.com","phone":"+1-555-0123","balance":1250.50}'
- name: body-transformer
config:
response:
input_format: json
template: |
{% local consumer_name = _ctx.consumer and _ctx.consumer.username or "" %}
{% if consumer_name == "aic_consumer-a" then %}
{
"user_id": {* user_id *},
"display_name": {* _escape_json(username) *},
"email": {* _escape_json(email) *}
}
{% elseif consumer_name == "aic_consumer-b" then %}
{
"user_id": {* user_id *},
"email": {* _escape_json(email) *},
"balance": {* balance *}
}
{% else %}
{* _body *}
{% end %}
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /mock
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: body-transformer-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: consumer-a
spec:
ingressClassName: apisix
authParameter:
keyAuth:
value:
key: consumer-a
---
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: consumer-b
spec:
ingressClassName: apisix
authParameter:
keyAuth:
value:
key: consumer-b
---
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: consumer-c
spec:
ingressClassName: apisix
authParameter:
keyAuth:
value:
key: consumer-c
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
ingressClassName: apisix
http:
- name: body-transformer-route
match:
paths:
- /mock
upstreams:
- name: httpbin-external-domain
plugins:
- name: key-auth
enable: true
- name: mocking
enable: true
config:
response_example: '{"user_id":1001,"username":"john_doe","email":"john@example.com","phone":"+1-555-0123","balance":1250.50}'
- name: body-transformer
enable: true
config:
response:
input_format: json
template: |
{% local consumer_name = _ctx.consumer and _ctx.consumer.username or "" %}
{% if consumer_name == "aic_consumer-a" then %}
{
"user_id": {* user_id *},
"display_name": {* _escape_json(username) *},
"email": {* _escape_json(email) *}
}
{% elseif consumer_name == "aic_consumer-b" then %}
{
"user_id": {* user_id *},
"email": {* _escape_json(email) *},
"balance": {* balance *}
}
{% else %}
{* _body *}
{% end %}
将配置应用到集群:
kubectl apply -f body-transformer-ic.yaml
❶ 配置 mocking 插件返回示例上游响应。
❷ 将响应输入格式设置为 JSON。
❸ 设置根据消费者身份自定义响应的转换模板。
发送带有不同 apikey 头的请求以验证响应转换:
以 consumer-a 身份发送请求:
curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-a"
你应该看到类似于以下的响应,展示了这些转换:
username字段已重命名为display_name- 敏感的
phone和balance字段已被过滤掉
{
"user_id": 1001,
"display_name": "john_doe",
"email": "john@example.com"
}
以 consumer-b 身份发送请求:
curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-b"
你应该看到类似于以下的响应,展示了这些转换:
username和phone字段已被过滤掉balance字段被保留
{
"user_id": 1001,
"email": "john@example.com",
"balance": 1250.50
}
以 consumer-c 身份发送请求:
curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-c"
你应该看到类似于以下的响应,显示原始响应被原样返回:
{
"user_id": 1001,
"username": "john_doe",
"email": "john@example.com",
"phone": "+1-555-0123",
"balance": 1250.50
}
基于消费者身份转换嵌套响应体
以下示例演示了如何根据不同的消费者身份自定义响应体转换。该示例展示了如何提取嵌套字段、重组数据结构并扁平化嵌套对象,同时根据消费者身份向不同的消费者提供不同的响应格式。
创建响应转换模板,该模板根据消费者身份提取并重组嵌套的 JSON 字段:
rsp_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr -d '\r\n'
{% local consumer_name = _ctx.consumer and _ctx.consumer.username or "" %}
{% if consumer_name == "consumer-a" then %}
{
"user_id": {* id *},
"user_name": {* _escape_json(name) *},
"email": {* _escape_json(profile.email) *},
"location": {
"city": {* _escape_json(profile.address.city) *},
"country": {* _escape_json(profile.address.country) *}
},
"created_at": {* _escape_json(metadata.created_at) *}
}
{% elseif consumer_name == "consumer-b" then %}
{
"id": {* id *},
"name": {* _escape_json(name) *},
"status": {* _escape_json(status) *},
"profile": {
"email": {* _escape_json(profile.email) *},
"address": {
"city": {* _escape_json(profile.address.city) *}
}
}
}
{% else %}
{* _body *}
{% end %}
EOF
)
- Admin API
- ADC
- Ingress Controller
创建配置了 key-auth 的消费者:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "consumer-a",
"plugins": {
"key-auth": {
"key": "consumer-a"
}
}
}'
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "consumer-b",
"plugins": {
"key-auth": {
"key": "consumer-b"
}
}
}'
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "consumer-c",
"plugins": {
"key-auth": {
"key": "consumer-c"
}
}
}'
使用嵌套结构模板创建一个带有 body-transformer、key-auth 和 mocking 插件的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "body-transformer-route",
"uri": "/mock",
"plugins": {
"key-auth": {},
"mocking": {
"response_example": "{\"id\":123,\"name\":\"John Doe\",\"status\":\"active\",\"profile\":{\"email\":\"john@example.com\",\"address\":{\"city\":\"New York\",\"country\":\"USA\"}},\"metadata\":{\"created_at\":\"2024-01-01\",\"tags\":[\"vip\",\"premium\"]}}"
},
"body-transformer": {
"response": {
"input_format": "json",
"template": "'"$rsp_template"'"
}
}
}
}'
创建配置了 key-auth 的消费者,并创建一个启用了 body-transformer、key-auth 和 mocking 插件的路由:
consumers:
- username: consumer-a
credentials:
- name: key-auth
type: key-auth
config:
key: consumer-a
- username: consumer-b
credentials:
- name: key-auth
type: key-auth
config:
key: consumer-b
- username: consumer-c
credentials:
- name: key-auth
type: key-auth
config:
key: consumer-c
services:
- name: body-transformer-service
routes:
- name: body-transformer-route
uris:
- /mock
plugins:
key-auth: {}
mocking:
response_example: '{"id":123,"name":"John Doe","status":"active","profile":{"email":"john@example.com","address":{"city":"New York","country":"USA"}},"metadata":{"created_at":"2024-01-01","tags":["vip","premium"]}}'
body-transformer:
response:
input_format: json
template: |
{% local consumer_name = _ctx.consumer and _ctx.consumer.username or "" %}
{% if consumer_name == "consumer-a" then %}
{
"user_id": {* id *},
"user_name": {* _escape_json(name) *},
"email": {* _escape_json(profile.email) *},
"location": {
"city": {* _escape_json(profile.address.city) *},
"country": {* _escape_json(profile.address.country) *}
},
"created_at": {* _escape_json(metadata.created_at) *}
}
{% elseif consumer_name == "consumer-b" then %}
{
"id": {* id *},
"name": {* _escape_json(name) *},
"status": {* _escape_json(status) *},
"profile": {
"email": {* _escape_json(profile.email) *},
"address": {
"city": {* _escape_json(profile.address.city) *}
}
}
}
{% else %}
{* _body *}
{% end %}
将配置同步到网关:
adc sync -f adc.yaml
创建配置了 key-auth 的消费者,并创建一个启用了 body-transformer、key-auth 和 mocking 插件的路由:
使用 Ingress Controller 配置消费者时,消费者名称会生成为 namespace_consumername 格式。因此,模板中的 consumer_name 逻辑应匹配这种格式的消费者名称。
- Gateway API
- APISIX CRD
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: consumer-a
spec:
gatewayRef:
name: apisix
credentials:
- type: key-auth
name: primary-key
config:
key: consumer-a
---
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: consumer-b
spec:
gatewayRef:
name: apisix
credentials:
- type: key-auth
name: primary-key
config:
key: consumer-b
---
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: consumer-c
spec:
gatewayRef:
name: apisix
credentials:
- type: key-auth
name: primary-key
config:
key: consumer-c
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: body-transformer-plugin-config
spec:
plugins:
- name: key-auth
config:
_meta:
disable: false
- name: mocking
config:
response_example: '{"id":123,"name":"John Doe","status":"active","profile":{"email":"john@example.com","address":{"city":"New York","country":"USA"}},"metadata":{"created_at":"2024-01-01","tags":["vip","premium"]}}'
- name: body-transformer
config:
response:
input_format: json
template: |
{% local consumer_name = _ctx.consumer and _ctx.consumer.username or "" %}
{% if consumer_name == "aic_consumer-a" then %}
{
"user_id": {* id *},
"user_name": {* _escape_json(name) *},
"email": {* _escape_json(profile.email) *},
"location": {
"city": {* _escape_json(profile.address.city) *},
"country": {* _escape_json(profile.address.country) *}
},
"created_at": {* _escape_json(metadata.created_at) *}
}
{% elseif consumer_name == "aic_consumer-b" then %}
{
"id": {* id *},
"name": {* _escape_json(name) *},
"status": {* _escape_json(status) *},
"profile": {
"email": {* _escape_json(profile.email) *},
"address": {
"city": {* _escape_json(profile.address.city) *}
}
}
}
{% else %}
{* _body *}
{% end %}
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /mock
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: body-transformer-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: consumer-a
spec:
ingressClassName: apisix
authParameter:
keyAuth:
value:
key: consumer-a
---
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: consumer-b
spec:
ingressClassName: apisix
authParameter:
keyAuth:
value:
key: consumer-b
---
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: consumer-c
spec:
ingressClassName: apisix
authParameter:
keyAuth:
value:
key: consumer-c
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: body-transformer-route
spec:
ingressClassName: apisix
http:
- name: body-transformer-route
match:
paths:
- /mock
upstreams:
- name: httpbin-external-domain
plugins:
- name: key-auth
enable: true
- name: mocking
enable: true
config:
response_example: '{"id":123,"name":"John Doe","status":"active","profile":{"email":"john@example.com","address":{"city":"New York","country":"USA"}},"metadata":{"created_at":"2024-01-01","tags":["vip","premium"]}}'
- name: body-transformer
enable: true
config:
response:
input_format: json
template: |
{% local consumer_name = _ctx.consumer and _ctx.consumer.username or "" %}
{% if consumer_name == "aic_consumer-a" then %}
{
"user_id": {* id *},
"user_name": {* _escape_json(name) *},
"email": {* _escape_json(profile.email) *},
"location": {
"city": {* _escape_json(profile.address.city) *},
"country": {* _escape_json(profile.address.country) *}
},
"created_at": {* _escape_json(metadata.created_at) *}
}
{% elseif consumer_name == "aic_consumer-b" then %}
{
"id": {* id *},
"name": {* _escape_json(name) *},
"status": {* _escape_json(status) *},
"profile": {
"email": {* _escape_json(profile.email) *},
"address": {
"city": {* _escape_json(profile.address.city) *}
}
}
}
{% else %}
{* _body *}
{% end %}
将配置应用到集群:
kubectl apply -f body-transformer-ic.yaml
❶ 配置 mocking 插件返回示例嵌套上游响应。
❷ 将响应输入格式设置为 JSON。
❸ 设置根据消费者身份处理嵌套 JSON 结构的转换模板。
以 consumer-a 身份发送请求以验证嵌套结构转换:
curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-a"
你应该看到类似于以下的响应,展示了这些嵌套字段转换:
profile.email已提取到顶层emailprofile.address.city和profile.address.country已组合成一个新的location对象metadata.created_at已提取到顶层created_at
{
"user_id": 123,
"user_name": "John Doe",
"email": "john@example.com",
"location": {
"city": "New York",
"country": "USA"
},
"created_at": "2024-01-01"
}
以 consumer-b 身份发送请求以验证嵌套结构转换:
curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-b"
你应该看到类似于以下的响应,展示了这些转换:
- 原始
profile对象结构被保留 profile.address.country和metadata字段已被过滤掉
{
"id": 123,
"name": "John Doe",
"status": "active",
"profile": {
"email": "john@example.com",
"address": {
"city": "New York"
}
}
}
以 consumer-c 身份发送请求以验证嵌套结构转换:
curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-c"
你应该看到类似于以下的响应,显示原始嵌套响应被原样返回:
{
"id": 123,
"name": "John Doe",
"status": "active",
"profile": {
"email": "john@example.com",
"address": {
"city": "New York",
"country": "USA"
}
},
"metadata": {
"created_at": "2024-01-01",
"tags": ["vip", "premium"]
}
}