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
ismultipart/form-data
. - The parsed data is available to plugins via the WorkOrder's
_multipartFormData
array.