Context

In most cases within API management, APIs exist out of a JSON or XML structured payloads. However, in some cases it can be beneficial or is even required to use payloads that are structured in multipart/form-data. For instance when a file is being transmitted. Each value is sent as a block of data (a part of the payload), with a user defined delimiter (a boundary) separating each part.

Example of multipart/form-data that contains a file:

--AkOYk0nEiGKnphwnsu4JClvCeSNl5IW
content-disposition: form-data; name="metadata"
content-type: application/json
content-length: 234

{"id":"7be0033f-d075-46fc-889f-b6835aa8ddf3","fileName":"My file.sepa","generationDate":"2020-11-27T08:13:54.887Z","type":"APA_ALLOWANCE","referencePeriod":{"startDate":"2020-11-27T08:13:54.887Z","endDate":"2020-11-27T08:13:54.887Z"}}
--AkOYk0nEiGKnphwnsu4JClvCeSNl5IW
content-disposition: form-data; name="file"; filename="1014393517061094530.tmp"
content-type: application/xml
content-length: 686

<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03">
    <CstmrCdtTrfInitn>
        <GrpHdr>
            <MsgId>3a3ddece-10c1-4c78-aee7-8fdbd629619</MsgId>
            <CreDtTm>2020-08-03T13:31:17.691042</CreDtTm>
            <NbOfTxs>1</NbOfTxs>
            <InitgPty>
                <Nm>Wal-Protect</Nm>
            </InitgPty>
        </GrpHdr>
    </CstmrCdtTrfInitn>
</Document>
--AkOYk0nEiGKnphwnsu4JClvCeSNl5IW—

Unfortunately, API management applications do not always support this form of payload and therefore standardly can’t parse them. However, by using JavaScript, we can come a long way to make it work.

How multipart/form-data is structured

Multipart/form-data consists out of a series of parts that are separated by a so-called boundary string. Each part is expected to contain a few headers. A required header is ‘content-disposition’, which indicates that the disposition type is ‘form-data’ and it also has a parameter ‘name’. If the data would be actual form data, the name parameter would contain an original field name of that form, for example: ‘userName’.  Optionally, headers like ‘content-type’ and ‘content-length’ can be added.

Next to the header section, a following part also contains actual data. This is splitted from the header section by an empty (blanc) line.

How to make it work

The payload of a message received in your API management tool doesn’t strictly need to be JSON or XML. Any payload form can be accepted. As such, we’re able to extract the body from the message and read it as a long string. In APIM we can make use of plain javascript code to help us. The following code does this and puts the string in a variable.

var bodyAsBlob = apim.getvariable("request.body").item(0);
var bodyAsString = bodyAsBlob.toBuffer().toString();

We can split up the string of data into separate variables, based on the boundary string, which has the form: ‘–AkOYk0nEiGKnphwnsu4JClvCeSNl5IW’. Of course, we need to know the boundary value before we can even start separating the body. This is simple to apprehend by looking for it in the header of the HTTP request message. On a properly formed HTTP messages with a multipart/form-data structure, this value should be added in the header ‘Content-Type’ and looks like this:

multipart/form-data;charset=UTF-8;boundary=AkOYk0nEiGKnphwnsu4JClvCeSNl5IW

Having the boundary value, we can continue with splitting up the body in separate variables, this all can be done by using plain old javascript.

var parts = bodyAsString.split(new RegExp(boundary));

If we look at our example, we can see 2 separate parts, with each a header section and a data section. This mostly is the case when transmitting files through multipart/form-data. We have a part with metadata (background information about the file) and a part with data of the file itself.

In our example we can see that the first part (the metadata) contains 3 headers and a JSON string:

content-disposition: form-data; name="metadata"
content-type: application/json
content-length: 234

{"id":"7be0033f-d075-46fc-889f-b6835aa8ddf3","fileName":"My file.sepa","generationDate":"2020-11-27T08:13:54.887Z","type":"APA_ALLOWANCE","referencePeriod":{"startDate":"2020-11-27T08:13:54.887Z","endDate":"2020-11-27T08:13:54.887Z"}}

Now, to get the data out of each part, we need to perform another split. But this time, there’s no delimiter based on characters that we can use. However, we can see that the data is divided by 2 new lines, which in string format is being indicated by either ASCII codes ‘\r’ (Carriage Return) and ’\n’ (Line Feed), or just one ‘\n’. It’s easy to check which one is being used.

var newLineCode = '\\r\\n';
if (bodyAsString.includes('\\n\\n')) { newLineCode = '\\n'; }

As we now put it all together and iterate over each separate part, splitting every part again on the double new line, we can extract the data itself.

As mentioned earlier, we can know and indicate the kind of data we’re extracting, by looking in the header section. By using a simple function we can match the pattern of the naming (name=”…”) to find it.

function Header_parse(header) {
        var headerFields = {};
        var matchResult = header.match(/^.* name="([^"]*)".*$/);
        if ( matchResult ) headerFields.name = matchResult[1];
        return headerFields;
}

Having the name and the data, we can assign the data value to a named variable (the name coming from the header), which we can put in an APIM variable after the iteration.

Finally, when all data is extracted and put in global variables, we can further use the data within the API functionality.

Author: Kevin van der Waals