Skip to main content
Version: 3.19.0

Convert Between JSON and SOAP XML

SOAP services exchange XML envelopes, but many HTTP clients use JSON. The body-transformer plugin can decode a JSON request, render a SOAP envelope, parse the XML response, and return a JSON document to the client.

This guide configures that two-way transformation with a sample country service.


Flow in which APISIX transforms a JSON request to SOAP XML and transforms the XML response to JSON

Prerequisite(s)​

  • Install Docker.
  • Install cURL to send requests to APISIX for validation.
  • Install Git to download the sample SOAP service.
  • Follow the Getting Started tutorial to start APISIX in Docker. The commands below use the apisix-quickstart-net network created by that tutorial.
  • If Admin API authentication is enabled, export an admin-role key as ADMIN_API_KEY. The Docker quickstart disables authentication for local evaluation; keep it enabled in production.

Deploy an Example SOAP Service​

Clone the Spring sample service and check out the revision tested in this guide:

git clone "https://github.com/spring-guides/gs-producing-web-service.git"
cd gs-producing-web-service/complete
git switch --detach c9c88ea61cfbcef0de6275c7b6c8b1320549017a

Run the service in the same Docker network as APISIX. The named volume caches Maven dependencies across container restarts:

docker volume create soap-maven-cache
docker rm -f quickstart-soap-service 2>/dev/null || true

docker run -d \
--rm \
--name quickstart-soap-service \
--network apisix-quickstart-net \
-v "$PWD:/workspace" \
-v soap-maven-cache:/root/.m2 \
-w /workspace \
eclipse-temurin:17-jdk@sha256:420933bf319e6aff4bec22eff8d2a31c824df5d853432e3dc0f46d6ea8dd5919 \
./mvnw spring-boot:run

Follow the startup logs:

docker logs -f quickstart-soap-service

Wait for Started ProducingWebServiceApplication, then press Ctrl+C. The application continues running in the background.

Create Transformation Templates​

Create the request template. It reads name from the decoded JSON body and renders the SOAP request expected by the sample service:

request-template.xml
cat > request-template.xml <<'EOF'
<?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

Create the response template. The plugin removes XML namespace prefixes before exposing response fields to the template:

response-template.json
cat > response-template.json <<'EOF'
{% 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

Encode the templates for safe insertion into the Admin API JSON document:

export REQUEST_TEMPLATE_BASE64="$(base64 < request-template.xml | tr -d '\r\n')"
export RESPONSE_TEMPLATE_BASE64="$(base64 < response-template.json | tr -d '\r\n')"

The templates use lua-resty-template syntax. The plugin also provides _escape_xml() and _escape_json() for context-appropriate escaping, _body for the original body, and _ctx for request context.

Configure APISIX​

Create a route with the body-transformer plugin:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<EOF
{
"id": "body-transformer-route",
"methods": ["POST"],
"uri": "/ws",
"plugins": {
"body-transformer": {
"request": {
"template": "$REQUEST_TEMPLATE_BASE64",
"template_is_base64": true,
"input_format": "json"
},
"response": {
"template": "$RESPONSE_TEMPLATE_BASE64",
"template_is_base64": true,
"input_format": "xml"
}
},
"proxy-rewrite": {
"uri": "/services",
"headers": {
"set": {
"Content-Type": "text/xml"
}
}
},
"response-rewrite": {
"headers": {
"set": {
"Content-Type": "application/json"
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"quickstart-soap-service:8080": 1
}
}
}
EOF

❶ Decode the client request as JSON before rendering the request template.

❷ Decode the upstream response as XML before rendering the response template.

❸ Send the transformed request to the sample service's /services endpoint with a SOAP-compatible content type.

❹ Return the transformed response with a JSON content type.

❺ Resolve the sample service by its container name on apisix-quickstart-net.

Verify​

Send a JSON request to the route:

curl "http://127.0.0.1:9080/ws" -X POST \
-H "Content-Type: application/json" \
-d '{"name":"Spain"}'

You should receive a response similar to the following:

{
"status": "200",
"currency": "EUR",
"population": 46704314,
"capital": "Madrid",
"name": "Spain"
}

The client sends and receives JSON. APISIX sends a SOAP XML request to the upstream service and transforms its XML response back to JSON.

Next Steps​

The body-transformer plugin can also transform other supported input formats or modify request and response bodies. See the Plugin Hub reference for its configuration options.