npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

fastify-xml-server

v1.5.1

Published

Fastify plugin which integrates xml2js and modifies your Fastify server to receive XML requests and send XML responses while still working with JSON data internally.

Downloads

57

Readme

fastify-xml-server

Fastify plugin which integrates xml2js and modifies your Fastify server to receive XML requests and send XML responses while still working with JSON data internally.

Contents:

XML Server VS SOAP Server

Even though this plugin allows to completely switch the communication data format of a server and handle errors with XML responses (and is definitely useful for majority of use cases where you need SOAP) it is missing one important feature of a complete SOAP server - which is the routing. Unlike REST it is not uncommon for SOAP to have a single URL path responsible for multiple operations and the decision on what operation to perform is made based on the payload of a request. So if you are facing a use case like that you would have to implement this decision logic in a single request handler or maybe find additional libraries for this purpose. Otherwise this plugin should satisfy the vast majorty of SOAP use cases, but this detail does not allow to call it SOAP server.

Usage

import fastify from 'fastify';
import { fastifyXmlServer } from 'fastify-xml-server';

const server = fastify({ logger: true });

server.register(fastifyXmlServer, { contentType: ['application/xml'] });

server.listen({ port: 3000 }, (err, address) => {
  server.log.info(`server listening on ${address}`);
});

Configuration

The plugin is configured with options object of XmlServerOptions interface:

export interface XmlServerOptions extends FastifyPluginOptions {
  parserOptions?: ParserOptions;
  serializerOptions?: BuilderOptions;
  errorTranslator?: (error: any) => Record<string, any>;
  wrapper?: Record<string, any>;
  contentType?: string[];
  assignOneElementArrays?: boolean;
  propagateRawXml?: boolean;
  dropNamespacePrefixes?: boolean;
}
  • parserOptions - config of xml2js parser (responsible for parsing xml requests to json) using xml2js ParserOptions interface without changes, this config is passed through directly to xml2js. default:

    {
      explicitRoot: false, // removes root wrapper element to avoid unnecessary property chaning in Javascript
      ignoreAttrs: true // removes xml tag attributes from resulting JSON object leaving only tag values
    }
  • serializerOptions - config of xml2js builder (responsible for serializing responses to xml string) using xml2js BuilderOptions interface without changes, this config is passed through directly to xml2js. default:

    {
      renderOpts: {
        pretty: false; // skips additional processing for linebreaks and indentation to improve performance and reduce response size
      }
    }
  • errorTranslator - a function responsible for translating errors into input objects for xml serializer. This function replaces standard Fastify error handler and allows to send xml even for some automatic error responses of Fastify.By default errorTranslator maps Node.js Error into SOAP 1.2 Fault schema (setting only Code and Reason tags).

  • wrapper - Node.js object describing top level xml wrapper tags. The plugin automatically wraps all of your responses into the specified wrapper object before proceeding to serialization to help you avoid creating boilerplate code and complicating response objects in your implementation. By default a wrapper object is the standard SOAP 1.2 Envelope and Body tags specified in standard SOAP 1.2 env namespace. default:

    {
      'env:Envelope': {
        $: {
          'xmlns:env': 'https://www.w3.org/2003/05/soap-envelope/',
        },
        'env:Body': '',
      },
    }

    Note: wrapper object is serialized using the same xml2js builder therefore you should use xml2js syntax for specifing attributes etc.

  • contentType - list of supported HTTP content types.

    default: ['application/xml', 'text/xml']

    Note: first element from the list is used to set Content-Type header on responses, all values represent supported request content types.

    It is possible that some clients might pass additional parameters in the Content-Type header such as charset. The behavior of the plugin in such cases in the following:

    • in case the type only part from the Content-Type header (ignoring parameters) is in the contentType list it is considered supported;
    • otherwise it is considered supported only if the contentType list contains the exact match with all parameters.

    In other words if specific parameters are not of much importance (which should be true for most use cases) - specifing a content type without parameters in the list would automatically support all of the parametrized variations of that type. For example contentType set to ['application/xml'] would implicitly support application/xml;charset=utf-8, application/xml;charset="utf-8", application/xml;charset=utf-16 etc. However if you need to accept only certain parametrized content types - just specify those explicitly in the contentType array.

  • assignOneElementArrays - xml2js parser always outputs all child elements as arrays, even in cases there the child of an element is a string value or there is just one child, this flag allows to improve this output by assigning all arrays with one element and all arrays with element values as values to their parent keys dropping the array syntax (which makes it much more operable, see example) default: true Example: Input xml:

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
      <soapenv:Header/>
      <soapenv:Body>
        <soapenv:detail>
          <soapenv:Value>1</soapenv:Value>
        </soapenv:detail>
      </soapenv:Body>
    </soapenv:Envelope>

    Standard xml2js parser output:

    {
      "soapenv:Header": [""],
      "soapenv:Body": [
        {
          "soapenv:detail": [
            {
              "soapenv:Value": ["1"]
            }
          ]
        }
      ]
    }

    Standard xml2js parser output with assignOneElementArrays enabled:

    {
      "soapenv:Header": "",
      "soapenv:Body": {
        "soapenv:detail": {
          "soapenv:Value": "1"
        }
      }
    }

    Note: because assignOneElementArrays executes a recursive function on a JSON tree it is limited in depth to 30 to avoid denial of service caused by too deep recusrion.

  • propagateRawXml - if enabled adds rawXml property containing origninal XML string to Fastify request object making it available in a handler function. default: false

  • dropNamespacePrefixes - if enabled all XML namespace prefixes in tag names (keys of the parsed JSON) will be removed. default: false

    XML being an extensible format allows to specify any namespaces a user needs and then use those namespaces in tag names in order to distinguish some of them to a higher degree (see XML namespaces). This means that theoretically different consumers of your API might be using different namespace prefixes for the same namespaces. For example there is a namespace for domain schemas of your API defined at http://xsd.org/domain and one consumer specifies it as xmlns:domain="http://xsd.org/domain" whereas another consumer specifies it as xmlns:dmn="http://xsd.org/domain" which means that tags of that namespace for those two consumers will look like <domain:entity> and <dmn:entity> respectively. From the XML perspective everything is consistent but when parsed to JSON this means that the keys of resulting JSON objects will be different for different requests since there is no concept of "key namespace" in JSON format. This fact may pose various problems like creating request schemas (in case of Fastify) or having some deterministic logic to process such request payloads at all. Therefore if your API uses some custom namespaces where there is a chance of different consumers using different namespace prefixes it is better to enable this option and the plugin will omit all of the namespace prefixes from JSON keys for you during request parsing allowing to have a static schema of JSON objects regardless of XML namespaces usage.

Getting original XML in request object

In order to get access to the original XML request string in your request handler or custom hook functions set propagateRawXml property in options object to true. After that is done the plugin will add the original XML into rawXml property of a Fastify request object. For usage with Typescript the plugin uses declaration merging of FastifyRequest interface inside of the d.ts file, which allows to reference rawXml property on a request.

import fastify from 'fastify';
import { fastifyXmlServer } from 'fastify-xml-server';

const server = fastify({ logger: true });

server.register(fastifyXmlServer, { propagateRawXml: true });

server.route({
  method: 'POST',
  url: '/call',
  handler: async (req, rep) => {
    console.log(req.rawXml);
    return rep.status(200).send(req.body['soapenv:Body']['soapenv:detail']);
  },
});

server.listen({ port: 3000 }, (err, address) => {
  server.log.info(`server listening on ${address}`);
});

Note: keep in mind that the original XML string might take up a considerable amount of memory.

Parse any xml on demand

You can invoke the same xml parsing which is configured for your Fastify instance on demand at any point in code using parseXml function. parseXml completely mimics the parsing logic you have configured for the Fastify plugin. However it is also possible to override some of the parsing logic by passing an optional configuration object of interface XmlParserOptions to parseXml which consists of a subset of XmlServerOptions parameters related to parsing logic.

export interface XmlParserOptions {
  parserOptions?: ParserOptions;
  wrapper?: Record<string, any>;
  assignOneElementArrays?: boolean;
  dropNamespacePrefixes?: boolean;
}

You can use this function even before the plugin is initialized, in this case it would behave according to the default configuration.

The function takes one argument which is an xml string and one type argument which specifies a return type of the function, by default it is Record<string, any>.

async function parseXml<T = Record<string, any>>(xml: string, options?: XmlParserOptions): Promise<T>;

Example usage:

import { parseXml } from 'fastify-xml-server';

const xml = '<env:Envelope><value>1</value></env:Envelope>';

(async () => {
  const json = await parseXml(xml);
})();

Under the hood

If you are familiar with Fastify this section should be interesting and informative for you, but also shuold help to understand what is heppening under the hood of the plugin and potentially contribute improvements or debug problems.

   The plugin makes 6 modifications of your Fastify server instance:

  1. Sets custom 404 error handler using setNotFoundHandler method of a Fastify instance, which generates error using errorTranslator passing not found error in it.
  2. Sets onRequest hook which executes request Content-Type check and rejects unsupported media types (in case no Content-Type header is present the check passes).
  3. Sets custom request parser (using xml2js) for the configured list of xml content types with the addContentTypeParser method of a Fastify instance.
  4. Sets custom error handler function with the setErrorHandler method of a Fastify instance, which uses the specified errorTranslator in order to generate object representation of XML error response and serializes it into XML.
  5. Sets custom reply serializer with the setReplySerializer method of a Fastify instance, which uses xml2js Builder in order to serialize response object.
  6. Sets onSend hook which adds the specified Content-Type header to every response.