Multipart form data (usually written
multipart/form-data) is an encoding format devised as a means of encoding an HTTP form for posting up to a server. It is often used to encode files for upload to web servers. The format is formally defined in RFC-7578 : Returning Values from Forms: multipart/form-data.
A HTTP POST message can include a HTTP
content-type header of type
multipart/form-data to indicate that the body is encoded in this way and the header must contain a directive to define the ‘boundary’ string used to separate the ‘parts’ of the body.
content-type: multipart/form-data; boundary="9871z7t355g08e925"
The boundary is any string of ASCII characters which is unlikely to appear in the data carried in the body. The quotes around the boundary string are only required if it contains special characters, such as colon (:).
Each part of the body is introduced by the boundary separator.
The sequence is:
CRLF, -- (two hyphens), boundary string e.g.
Each part has one or more headers which indicate the encoding of that part and which follow immediately after the boundary separator. The content-disposition header is mandatory and must have a value of form-data ; it may also include a directive to specify the name of the field on the form e.g.
--9871z7t355g08e925 content-disposition: form-data; name="user_name"
When the form field is for a file to upload, it’s also common practice to include a filename directive, which suggests the name to use to store the file data on the receiving system.
--9871z7t355g08e925 content-disposition: form-data; name="profile_picture"; filename="my_passport_photo.jpg"
Another common (though optional) part header is content-type which is used to indicate the media-type of that part.
e.g. for text content
--9871z7t355g08e925 content-disposition: form-data; name="first_name" content-type: text/plain
e.g. for binary content
--9871z7t355g08e925 content-disposition: form-data; name="details"; filename="config01.txt" content-type: application/octet-stream
Binary data carried within
multipart/form-data formatted messages is not encoded in any way, it is the raw sequence of binary bytes; it is only the presence of the boundary separator which terminates the sequence.
The end of a part is indicated by the occurrence of the next boundary separator.
The final part of the body is terminated with a boundary separator which is immediately followed by two hyphens.
The sequence is:
CRLF, -- (two hyphens), boundary string, -- e.g.
In order to use
multipart/form-data we need to be able to construct a HTTP Request body which consists of a mixture of text and (probably) binary content. The outbound
RESTMessageV2 API does not give us this level of control; we can set the outgoing request body as a string or we can set the body from an attachment.
The string methods will not accept binary data but the
setRequestBodyFromAttachment method does give us a means of supporting this if we can construct an attachment whose contents are exactly the multipart body that we wish to send to the target system.
There are no native Attachment APIs to feed a combination of text and binary data into an attachment but we can use
RESTMessageV2#saveResponseBodyAsAttachment to stream the Response of an HTTP call into an attachment. So, if we can call a REST API which constructs the form data body then we are in business.
As it happens, the scripted REST API objects do allow us to selectively stream a combination of text and binary data into an HTTP response using the
writeStream methods of the
RESTAPIResponseStream object, which we can obtain from the
So, we can implement this mechanism in the following convoluted manner:
- Set up a scripted REST API which takes text and attachments and generates a response that looks like the multipart body that we wish to ultimately send
- We call that API (as a loopback REST call to the same instance) with parameters which define the text and attachments (and boundary string) that we want to work with
- The API writes the combination of text and attachment data as a stream into its response
- We save the response from this loopback API into a temporary attachment (which will contain the exact multipart body that we wanted)
- We take the temporary attachment and stream that to the ultimate destination (we will need to know the boundary string that was embedded in the multipart body and include that in the content-type header of the request that we are sending)
The logic to support this implementation is in
wsMultipartHelper which contains helper methods for both the client code which wants to generate multipart data and for the REST API server side which generates the body as a response.
var mp = new wsMultipartHelper(); // We need a record to link the temporary attachment to. // (Default is the the sys_user record of the calling user) // For Unifi this is likely to be the HTTPRequest record // // Let's hang it off a record we created earlier and stored in 'grHost' // mp.setHostRecord(grHost); // Alternatively, we could call setHostDetails( table_name, sys_id ) // When adding an attachment we need to specify two or three things // - the name of the form field that the target is expecting // - the sys_id of the attachment to add // - (optional) the file name; if not supplied it will be taken from the attachment mp.addAttachment('file','0e329292398101808125852108518'); // We could add more text or attchment parts here // Generate the temporary attachment mp.createBody(); // We can now use the generated attachment along with the boundary string // to send the multipart body to the other system var req = new sn_ws.RESTMessageV2(); req.setHttpMethod('POST'); // getContentType returns the content-type string including the boundary // e.g. multipart/form-data; boundary="xxxxxxxxxxxx" req.setRequestHeader('Content-Type',mp.getContentType()); // getBodyId returns the sys_id of the multipart attachment req.setRequestBodyFromAttachment(mp.getBodyId()); req.setEndpoint( /* the url */ ); req.setAuthenticationProfile('basic',/* the auth profile */); var resp = req.execute(); // Once we have sent the body we can delete the temporary attachment mp.deleteBody();