将 JSON 转换为 XML
XML (可扩展标记语言) 是一种广泛采用的标准,用于表示和组织结构化数据。凭借其灵活性和人类可读的语法,XML 为跨各种系统和平台的数据交换和存储提供了一种通用的解决方案。它也被 SOAP 协议用作其消息格式。
本指南将向你展示如何使用 body-transformer 插件在 JSON 和 XML SOAP 之间进行转换,这允许客户端在与 SOAP 服务交互时发送和接收 JSON 数据。

前置条件
- 安装 Docker。
- 安装 cURL 以向 APISIX 发送请求进行验证。
- 安装 Java 17 用于示例 SOAP 服务器。
- 按照 快速入门教程 在 Docker 或 Kubernetes 中启动一个新的 APISIX 实例。
部署示例 SOAP 服务器
启动一个 示例 SOAP 服务器,该服务器公开来自各个欧洲国家的数据:
cd /tmp
git clone https://github.com/spring-guides/gs-soap-service.git
cd gs-soap-service/complete
./mvnw spring-boot:run
创建转换模板
转换模板主要使用 lua-resty-template 语法。有关更多信息,请参阅 模板语法。
此外,你可以在模板中使用一些辅助函数:
_escape_json()和_escape_xml()- 用于转义特殊字符,例如双引号_body- 用于访问请求正文_ctx- 用于访问上下文变量
创建请求和响应转换模板,这些模板是自定义的,并在 body-transformer 插件中配置,以指示如何在 XML 和 JSON 之间进行转码:
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="http://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
)
配置 APISIX
- Admin API
- 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",
"methods": ["POST"],
"uri": "/ws",
"plugins": {
"body-transformer": {
"request": {
"template": "'"$req_template"'",
// Annotate 1
"input_format": "json"
},
"response": {
"template": "'"$rsp_template"'",
// Annotate 2
"input_format": "xml"
}
},
"proxy-rewrite": {
"headers": {
"set": {
// Annotate 3
"Content-Type": "text/xml"
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
// Annotate 4
"localhost:8080": 1
}
}
}'
❶ 将请求输入格式设置为 JSON,以便插件在内部应用 JSON 解码器。
❷ 将响应输入格式设置为 XML,以便插件在内部应用 XML 解码器。
❸ 将 Content-Type 标头设置为 text/xml,以便上游服务正确响应。
❹ 更新你的 SOAP 服务的连接地址。
如果调整复杂的文本文件以使其成为有效的转换模板很麻烦,你可以使用 base64 实用程序对文件进行编码,例如:
"body-transformer": {
"request": {
"template": "'"$(base64 -w0 /path/to/request_template_file)"'"
},
"response": {
"template": "'"$(base64 -w0 /path/to/response_template_file)"'"
}
}
创建一个带有 body-transformer 插件的路由的 Kubernetes 清单文件:
- Gateway API
- APISIX CRD
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: ingress-apisix
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="http://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: ingress-apisix
name: ws-external-domain
spec:
type: ExternalName
externalName: host.docker.internal
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: ingress-apisix
name: body-transformer-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- method: POST
- path:
type: Exact
value: /ws
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: json-xml-plugin
backendRefs:
- name: ws-external-domain
port: 8080
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: ingress-apisix
name: ws-external-domain
spec:
externalNodes:
- type: Domain
name: host.docker.internal
port: 8080
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: ingress-apisix
name: body-transformer-route
spec:
ingressClassName: apisix
http:
- name: body-transformer-route
match:
paths:
- /ws
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="http://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
将配置应用到你的集群:
kubectl apply -f soap-route.yaml
验证
向路由发送带有有效 JSON 正文的请求:
curl "http://127.0.0.1:9080/ws" -X POST -d '{"name": "Spain"}'
你应该看到类似以下的响应:
{
"status": "200",
"currency": "EUR",
"population": 46704314,
"capital": "Madrid",
"name": "Spain"
}
这表明请求中发送的 JSON 正文已转换为 XML 并转发到上游 SOAP 服务,响应正文已从 XML 转换回 JSON。
下一步
body-transformer 插件还可以用于 YAML 和 JSON 之间的转换,或修改请求正文。有关更多信息,请参阅 插件文档。