Skip to content

Posting multipart/form-data from a Script

When an external API expects multipart/form-data (file uploads, mixed file+text fields, anything that would be a <form enctype="multipart/form-data"> in a browser) build the request with qie.getMultipartBuilder(), add each part with qie.addMultipartContentBinary / qie.addMultipartContentText, and hand the builder straight to qie.callRESTWebService. QIE finalizes the entity, generates the boundary, and sets the Content-Type header for you.

The pattern that trips people up is calling .build() themselves and then passing entity.getContentType() as the contentType argument. That works, but it is not necessary, and entity.getContentType() returns a plain String, not a Java ContentType object, so chaining .getValue() on it fails with TypeError: Cannot find function getValue in object multipart/form-data; .... Pass the builder directly and let callRESTWebService do the rest.

Core pattern

A file upload with one text field. The builder is the content argument; null for contentType tells QIE to use the multipart Content-Type the builder generates (with the matching boundary).

var url     = qie.getWsEndpointUrl('DocumentService');
var params  = qie.newParameterMap();
var builder = qie.getMultipartBuilder();

qie.addMultipartContentBinary(builder, 'file', fileBytes, 'application/pdf', 'report.pdf');
qie.addMultipartContentText(builder, 'description', 'Monthly report');

var response = qie.callRESTWebService(
   'DocumentService',
   url,
   'POST',
   builder,
   null,
   params,
   60000
);

messageCache.setValue('apiResponse', response);

Pass the builder, not the built entity

qie.callRESTWebService recognizes a MultipartEntityBuilder as the content argument and finalizes it internally, including the Content-Type and boundary. Calling .build() yourself and passing the resulting HttpEntity plus a manually-supplied content type works in most cases, but QIE then has to reconcile the two, which is the source of the boundary-mismatch errors people hit. Pass the builder, pass null for contentType.

Adding custom HTTP headers

Headers like X-API-Key, Authorization, or a custom tenant header go into the parameter map with the prefix http.header.. The map argument doubles as the parameter source for URL-encoded REST calls; for multipart requests, only the http.header.* entries are relevant.

var params = qie.newParameterMap();

params.put('http.header.X-API-Key', qie.getVariable('imagingApiKey'));

var response = qie.callRESTWebService(
   'ImagingService',
   url,
   'POST',
   builder,
   null,
   params,
   60000
);

Multiple files in one field

Some APIs expect the same field name repeated, e.g. files with two .dcm attachments. Call addMultipartContentBinary twice with the same field name. Each call adds another part.

qie.addMultipartContentBinary(builder, 'files', file1Bytes, 'application/dicom', 'image1.dcm');
qie.addMultipartContentBinary(builder, 'files', file2Bytes, 'application/dicom', 'image2.dcm');
qie.addMultipartContentText(builder, 'reason_for_exam', '');

If the API rejects an empty-string text part with a body-parsing error, drop the addMultipartContentText call entirely rather than sending an empty value, the two behaviors look the same to the user but are different on the wire.

Sending DICOM as a file

A DICOM message received over DIMSE is not the same byte sequence as a .dcm file on disk. message.getBytes() on a DICOM message returns QIE's internal dataset format. To send the object as a DICOM Part 10 file, which is what an API expects when it wants a .dcm upload. Use message.getDICOMBytesForFile(). The returned bytes include the DICOM preamble and File Meta Information data set that Part 10 readers require.

var url     = qie.getWsEndpointUrl('ImagingService');
var params  = qie.newParameterMap();
var builder = qie.getMultipartBuilder();

params.put('http.header.X-API-Key', qie.getVariable('imagingApiKey'));

qie.addMultipartContentBinary(
   builder,
   'files',
   message.getDICOMBytesForFile(),
   'application/dicom',
   source.getNode('SOPInstanceUID') + '.dcm'
);

var response = qie.callRESTWebService(
   'ImagingService',
   url,
   'POST',
   builder,
   null,
   params,
   60000
);

messageCache.setValue('apiResponse', response);

getBytes() vs getDICOMBytesForFile()

If the receiver responds with File meta information header missing or DcmFileFormat loading failed, the upload is sending QIE's internal DICOM bytes instead of a Part 10 file. Switch the byte source to message.getDICOMBytesForFile(). The same rule applies anywhere DICOM bytes leave QIE as a .dcm file: multipart upload, HTTP body, blob storage, file write.

Capturing the response

qie.callRESTWebService returns the response body as a String by default. Stash it in messageCache if downstream nodes need it; parse the JSON or XML once and cache individual fields if downstream nodes only need a few values.

messageCache.setValue('apiResponse', response);

var responseJson = qie.parseJSONString(response);
messageCache.setValue('apiCaseId', responseJson.getNode('/case_id'));
messageCache.setValue('apiStatus', responseJson.getNode('/status'));

To inspect the HTTP status code as well (for example to branch on 4xx vs 5xx) pass true for the returnFullHttpResponse flag and read the response object's status and body separately. See the Web Service Functions section of the Code Wizard reference for the full signature and the related multipart, header, and timeout options.