Splitting multipart binary form data into separate entries

Multipart Form Data

Preliminaries

This note describes how and when the server processes request bodies that are encoded as type multipart/form-data

When an HTML form is submitted by a browser, the browser chooses how to encode the data being sent by examining the form's enctype attribute.

Two values are meaningful:

  • application/x-www-form-urlencoded: for simple key/value pairs from text fields.
  • multipart/form-data: for data files that may contain binary data.

If the HTML form contains an input element of type=file, the encoding type should be multipart/form-data.

The server's multipart form data handler is responsible for parsing this type of request body into MultipartEntry objects. When successful, each form field and each binary file will have its own MultipartEntry added to the server's _multipartFormData array.

See the separate note about Multipart Entry objects for details.

HTML sample

Consider an HTML page with this form:

<html>
<form method=POST action='/api/save-form-data' enctype='multipart/form-data'>
<input type=text name='accountNumber' value='ES-ABC-DEFGH'>
<input type=text name='accountName' value='José María Álvarez'>
<input type=file name='avatar'>
<input type=submit Submit>
</form>
</html>

Router

The server could be configured to route incoming POSTs to a custom plugin to handle the saving of this data. The server config might look like this:

server {
plugins {
my-custom-handler {
location `/srv/www.example.com/plugins/my-custom-handler/index.js`
}
router {
`/api/*` *methods=POST *plugin=my-custom-handler
}
}
}

Server headers

When the server receives the user's submittal, the built-in multipart-form-data-handler will examine the content-type to see if it is multipart/form-data.

This header must also contain a boundary specifier, which will automatically be generated when using JavaScript's FormData object. For example:

content-type: multipart/form-data; boundary=----WebKitFormBoundaryxo9i2ubJdeXAprxP

If this content-type and boundary are suitable, the server's multipart-form-data-handler will be invoked.

Request body parsing

The request body is a Buffer, which will look something like this for the above HTML sample form:

------WebKitFormBoundaryxo9i2ubJdeXAprxP
Content-Disposition: form-data; name="accountNumber"

ES-ABC-DEFGH
------WebKitFormBoundaryxo9i2ubJdeXAprxP
Content-Disposition: form-data; name="accountName"

José María Álvarez
------WebKitFormBoundaryxo9i2ubJdeXAprxP
Content-Disposition: form-data; name="avatar"; filename="avatar.png"
Content-Type: image/png

�PNG

IHDR
........................
...................
............
.....
IEND�B`�
------WebKitFormBoundaryxo9i2ubJdeXAprxP--

The parser will split this payload into three parts, using the boundary string as the separator.

Each part will be assembled into a MultipartEntry object, and added to the workOrder's _multipartFormData array.

Work order

A custom plugin might process these entries with code like this:

import fs from 'fs';        
class MyCustomHandler {
async processingSequence(workOrder) {

for (let i=0; i < workOrder.multipartSize(); i++) {
const multipartEntry = workOrder.getMultipartEntry(i);

const name = multipartEntry.name; // accountNumber, accountName or avatar

if (name == 'accountNumber' || name == 'accountName') {
const value = multipartEntry.dataStr; // ES-ABC-DEFGH or José María Álvarez
}
else if (name == 'avatar') {
const name = multipartEntry.filename; // avatar.png
const bytes = multipartEntry.dataBytes; // binary Buffer
fs.writeFileSync(filename, bytes);
}
}
}
}

Configuration

There are no configurable options associated with this handler.

Review

Key points to remember:

  • The multipart-form-data handler is automatically invoked for POST requests when the content-type is multipart/form-data.
  • The parsed data is available to plugins via the WorkOrder's _multipartFormData array.
0

Splitting multipart binary form data into separate entries

🔗 🔎