Skip to content

Latest commit

 

History

History
289 lines (243 loc) · 8.82 KB

body-transformer.md

File metadata and controls

289 lines (243 loc) · 8.82 KB
title keywords description
body-transformer
Apache APISIX
API Gateway
Plugin
BODY TRANSFORMER
body-transformer
This document contains information about the Apache APISIX body-transformer Plugin.

Description

This plugin is used to transform the request and/or response body from one format to another format, e.g. JSON to XML.

Use cases:

  • simple SOAP proxy
  • generic template-based transform, e.g. JSON to JSON, JSON to HTML, XML to YAML

Attributes

Name Type Required Description
request object False request body transformation configuration
request.input_format string False request body original format, if not specified, it would be determined from Content-Type header.
request.template string True request body transformation template
request.template_is_base64 boolean False Set to true if the template is base64 encoded
response object False response body transformation configuration
response.input_format string False response body original format, if not specified, it would be determined from Content-Type header.
response.template string True response body transformation template
response.template_is_base64 boolean False Set to true if the template is base64 encoded

Enable Plugin

You can enable the Plugin on a specific Route as shown below:

:::note You can fetch the admin_key from config.yaml and save to an environment variable with the following command:

admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')

:::

curl http://127.0.0.1:9180/apisix/admin/routes/test_ws \
    -H "X-API-KEY: $admin_key" -X PUT -d '
{
    "methods": ["POST"],
    "uri": "/ws",
    "plugins": {
        "body-transformer": {
            "request": {
                "template": "..."
            },
            "response": {
                "template": "..."
            }
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "localhost:8080": 1
        }
    }
}'

Configuration description

The request and response correspond to configurations of request body and response body transformation perspectively.

Specify one of them, or both of them, to fit your need.

request/response:

  • input_format specifies the body original format:
    • xml (text/xml)
    • json (application/json)
  • template specifies the template text used by transformation.

Notes:

{{ ... }} in lua-resty-template will do html-escape, e.g. space character, so if it's not what you wish, use {* ... *} instead.

If you do not specify input_format and no Content-Type header, or body is nil, then this plugin will not parse the body before template rendering. In any case, you could access body string via {{ _body }}.

This is useful for below use cases:

  • you wish to generate body from scratch based on Nginx/APISIX variables, even if the original body is nil.
  • you wish to parse the body string yourself in the template via other lua modules, e.g. parse protobuf.

For example, parse YAML to JSON yourself:

{%
    local yaml = require("lyaml")
    local body = yaml.load(_body)
%}
{"foobar":"{{body.foobar.foo .. " " .. body.foobar.bar}}"}

You must ensure template is a valid JSON string, i.e. you need to take care of special characters escape, e.g. double quote. If it's cumbersome to escape big text file or complex file, you could use encode your template text file in base64 format instead.

For example, you could use base64 command to encode your template text file:

curl http://127.0.0.1:9180/apisix/admin/routes/test_ws \
    -H "X-API-KEY: $admin_key" -X PUT -d '
{
    "methods": ["POST"],
    "uri": "/ws",
    "plugins": {
        "body-transformer": {
            "request": {
                "template": "'"$(base64 -w0 /path/to/my_template_file)"'"
            }
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "localhost:8080": 1
        }
    }
}'

In template, you can use below auxiliary functions to escape string to fit specific format:

  • _escape_json()
  • _escape_xml()

Note that _escape_json() would double quote the value of string type, so don't repeat double-quote in the template, e.g. {"foobar":{*_escape_json(name)*}}.

And, you can refer to _ctx to access nginx request context, e.g. {{ _ctx.var.status }}.

Example

Let's take a simple SOAP proxy as example.

  • from downstream to upstream, it transforms the request body from JSON to XML
  • from upstream to downstream, it transforms the response body from XML to JSON
    • the response template distinguishes the normal response from the fault response

Run a test web service server

cd /tmp
git clone https://github.com/spring-guides/gs-soap-service.git
cd gs-soap-service
./mvnw spring-boot:run

Test

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
)

curl http://127.0.0.1:9180/apisix/admin/routes/test_ws \
    -H "X-API-KEY: $admin_key" -X PUT -d '
{
    "methods": ["POST"],
    "uri": "/ws",
    "plugins": {
        "proxy-rewrite": {
            "headers": {
                "set": {
                    "Accept-Encoding": "identity",
                    "Content-Type": "text/xml"
                }
            }
        },
        "response-rewrite": {
            "headers": {
                "set": {
                    "Content-Type": "application/json"
                }
            }
        },
        "body-transformer": {
            "request": {
                "template": "'"$req_template"'"
            },
            "response": {
                "template": "'"$rsp_template"'"
            }
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "localhost:8080": 1
        }
    }
}'

curl -s http://127.0.0.1:9080/ws -H 'content-type: application/json' -X POST -d '{"name": "Spain"}' | jq
{
  "status": "200",
  "currency": "EUR",
  "population": 46704314,
  "capital": "Madrid",
  "name": "Spain"
}

# Fault response
curl -s http://127.0.0.1:9080/ws -H 'content-type: application/json' -X POST -d '{"name": "Spain"}' | jq
{
  "message": "Your name is required.",
  "code": "SOAP-ENV:Server"
}

Delete Plugin

To remove the body-transformer Plugin, you can delete the corresponding JSON configuration from the Plugin configuration. APISIX will automatically reload and you do not have to restart for this to take effect.

curl http://127.0.0.1:9180/apisix/admin/routes/test_ws \
    -H "X-API-KEY: $admin_key" -X PUT -d '
{
    "methods": ["POST"],
    "uri": "/ws",
    "plugins": {},
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "localhost:8080": 1
        }
    }
}'