Awesome
nginx-xml-json
Proof-of-concept solution for presenting XML services as a JSON API. This showcases:
- Using Node.js (npm) modules to extend the NGINX JavaScript module (njs)
js_header_filter
andjs_body_filter
directives (requires njs 0.5.2 or later)
Using npm
This PoC uses the xml-js
Node.js module as a library to perform transformation between XML and JSON formats. The NGINX JavaScript module can use npm modules, provided that njs supports the ECMAScript objects and primitives that were used.
In this case we can use xml-js
as-is, and keep that code in a separate file for ease of maintenance. The Dockerfile includes the necessary steps to produce the module in a way that can be consumed by other njs functions. To do this manually, follow these steps:
- Install Node.js
- Obtain the
xml-js
module
$ npm install xml-js
- Create a single JavaScript file with one extra line of code that makes the module available in the
global
namespace. Instead of usingrequire('xml-js')
we can now useglobal.xmljs
$ echo "global.xmljs = require('xml-js');" | npx browserify -d -o xml-js.js -
- Export an empty function so that the global namespace from this file is available to all other njs files. The name of this function is not important.
$ cat << EOF >> xml-js.js
export default {xj}
function xj(){}
EOF
Learn more about using Node.js modules with njs.
Transforming XML responses (GET-only)
For XML services that offer a read-only (GET
) interface, i.e. clients don't send request bodies, we can use js_body_filter
to examine and modify the responses. The function is called for every buffer (part of the response) and so to perform full transformation of the response we must wait until we receive the last byte, indicated with flags.last
. At this point we can use r.sendBuffer()
to send whatever we like to the client.
js_body_filter
can be used inside a proxy_pass
location and so requires minimal config changes.
As the size of the response is likely to change, and the response format is different it is also important to modify the response headers, not just the body. We can use the js_header_filter
directive to call a separate function for this. The Content-Length
response header is removed so we rely on chunked encoding instead. The Content-Type
response header is replaced with application/json
to match the new body.
See the /api/f1
configuration for an example
Transforming requests and responses
For URIs that may also receive a request body that requires transformation (as well as the response) we cannot rely on js_body_filter
as that only handles responses. Bi-directional transformations can be achieved by splitting the location
block into three parts:
- A
location
that handles configuration for the client-to-nginx processing (all of the pre-content phases) and delegates content tojs_content
that executes; - JavaScript code that modifies the request and response. This code proxies to the backend by making a subrequest to;
location
that handles configuration for the nginx-to-backend processing
See the /echo
configuration for an example
Using this repo
Build
Clone this repo, then
$ docker build -t nginx:xmljs .
Run
$ docker run -d -p 8000:80 -v $PWD:/etc/nginx/conf.d nginx:xmljs
Test
$ curl localhost:8000/api/f1/circuits/silverstone
$ curl localhost:8000/echo
$ curl localhost:8000/echo -d '{"foo":"bar"}'