Outbound REST - Multipart/form-data

Rshear
Kilo Expert

Hi,

Currently working on an outbound REST to 3rd party tool (through a mid server). 3rd party requires mutipart to be sent when sending a file to attach/upload.

Test the POST call on postman and all ok. When in servericenow I'm getting no-where quick 🙂

That said I can get some kind of response just not the one I want (i.e. file uploaded). See REST content:

-----WebKitFormBoundaryAAA

Content-Disposition: form-data; name="file"; filename=${filename};

Content-Type:${contenttype}

${file}

-----WebKitFormBoundaryAAA

 

${contenttype} comes from the content type of the attachment on the sys_attachment table and ${file} is created using the below (which is in a script include):

-----------------------------------------------------------------------------------

var grAtt = new GlideRecord('sys_attachment');
grAtt.addQuery('table_sys_id', sys_id of attachment);
grAtt.query();

if(grAtt.next()){
var gsa = GlideSysAttachmentInputStream(grAtt.sys_id.toString());
var bytesContent = new Packages.java.io.ByteArrayOutputStream();

gsa.writeTo(bytesContent);
bytesContent.close();

//Get the file content
var mycontentStr = new Packages.java.lang.String(bytesContent.toByteArray());

i then set mycontentStr as a parameter to pass to the rest call.

 

Any help would be appreciated!...ive scoured the community and read a few things that multipart not supported and to use MId server script include however there seems to be conflicting views on this.

31 REPLIES 31

Swetang
Tera Contributor

We can send the multipart attachment from servicenow outbound rest message. The only condition is that the body should be in correct format. It can be sent as string. Below is the correct format:

 

'------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition:form-data; name="file"; filename="Capture.JPG";
Content-Type:image/jpeg

base64 encoded data 

------WebKitFormBoundary7MA4YWxkTrZu0gW--'

 

In Code we can use it like this:

 

'------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition:form-data; name="file"; filename=' + '"' + current.file_name + '"' + ';' + '\r\nContent-Type:' + current.content_type +'\r\n\r\n'+ encData + '\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--';

Kamil26
Giga Contributor

Hi Swetang,

 

Could you provide full script for this?

 

Unfortunately it not work for me..

 

BR,

Kamil

Swetang
Tera Contributor

Hi,

 

Please go through the below link:

 

https://developer.servicenow.com/dev_app.do#!/share/contents/8774514_multipart_upload_helper?v=1.0&t...

 

I ended up using this. This works like a charm. you just need to use the update set and a business rule. Please let me know if you need help with this.

Kamil26
Giga Contributor

Hi Swetan,

 

I have this imported, but it wont work for me.

 

Could you provide business rule which hits this script? Also are you changing something in Scripted Rest Api?

 

BR,

Kamil

Swetang
Tera Contributor

Hi @Kamil 

 

Sorry for the late reply:

 

Below is the BR for the same:

var table_name = current.table_name;
if(table_name == 'incident') // done for incident
{

var sys = current.table_sys_id;
var gr = new GlideRecord('incident');
gr.addQuery('sys_id', sys);

gr.query();
if (gr.next()) {
var hostRecord = new GlideRecord('xxx'); //name of any table 
hostRecord.get('621b89c6db6950905427b6d3f39614543'); // sysID of any record of the same table

 

 


var resp = doUpload(hostRecord, current.sys_id);

current.u_status='Success';
current.update();

}
}



function doUpload(hostRecord, attachmentId) {
var mpHelper = new global.MultipartHelper();

mpHelper.setHostRecord(hostRecord);
mpHelper.addAttachment('file', attachmentId);

// this uses the loopback API to create a multipart-formatted payload.
mpHelper.createBody();

// we create the real rest message, and attach the multipart attachment.
var restMessage = new sn_ws.RESTMessageV2();


restMessage.setEndpoint('xxx'); //URL of the endpoint


restMessage.setBasicAuth('username','password')
restMessage.setHttpMethod('POST');
restMessage.setRequestHeader("file", current.file_name);
restMessage.setRequestHeader("Multipart", "true");


// getContentType returns the content-type string including the boundary
// e.g. multipart/form-data; boundary="xxxxxxxxxxxx"
restMessage.setRequestHeader('Content-Type', mpHelper.getContentType());

// getBodyId returns the sys_id of the multipart attachment
restMessage.setRequestBodyFromAttachment(mpHelper.getBodyId());

 

var restResponse = restMessage.execute();

// Once we have sent the body we can delete the temporary attachment
mpHelper.deleteBody();

return restResponse.getBody();


}

 

 

Below is the MultipartHelper script: There is  a small change that you need to do:

 

 

var MultipartHelper = Class.create();
MultipartHelper.prototype = {

// these store the attachment we're tryign to format as a multipart request.
attachmentId: null,
attachmentGR: null,

// the host record is where the temporary attachment is stored after it's recieved from the loopback API
grHostRecord: null,

// this is the ID of the "multipart" attachment that should be included in outgoing POST.
bodyAttachmentId: null,

initialize: function() {
this.boundaryId = this._getBoundaryId();

},

// 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
setHostRecord: function(grHostRecord) {
this.grHostRecord = grHostRecord;
},
// Alternatively, we could call setHostDetails( table_name, sys_id )
setHostDetails: function(hostTable, hostSysId) {
var grHostRecord = new GlideRecord(hostTable);
if (grHostRecord.get(hostSysId)) {
this.grHostRecord = grHostRecord;
return true;
}
return false;
},

// 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
addAttachment: function(formName, attachmentId) {

this.formName = formName;
this.attachmentId = attachmentId;

this.attachmentGR = this._getAttachmentGR(this.attachmentId);
if (this.attachmentGR) {

this.attachmentName = this.attachmentGR.file_name.toString();

return true;
}

return false;
},

// Generate the temporary attachment
createBody: function() {


// make sure we have already looked up the attachment
if (!this.attachmentGR) {
return false;
}

// generate a message to the loopback api
var request = new sn_ws.RESTMessageV2();

request.setEndpoint(gs.getProperty('glide.servlet.uri') + 'api/xxx/multiparthelper/get'); //---> Use scripted API endpoint 

 


request.setHttpMethod('GET');

 


request.setBasicAuth('username', 'password'); //------------> provide your authentication here

 


request.setRequestHeader("Accept", "multipart/form-data");

// set the metadata for what should be included
request.setQueryParameter('attachment_id', this.attachmentId);
request.setQueryParameter('boundary_id', this.boundaryId);
request.setQueryParameter('form_name', this.formName);

// we write the response back into an attachment
request.saveResponseBodyAsAttachment(this.grHostRecord.getTableName(), this.grHostRecord.sys_id, this.attachmentName);

var response = request.execute();

 


// only continue if the result was 200
if (response.getStatusCode() == 200) {
this.bodyAttachmentId = response.getResponseAttachmentSysid();

return true;
}

return false;
},

// getContentType returns the content-type string including the boundary
// e.g. multipart/form-data; boundary="xxxxxxxxxxxx"
getContentType: function() {
return 'multipart/form-data; boundary=' + this.boundaryId;
},

// getBodyId returns the sys_id of the multipart attachment
getBodyId: function() {
return this.bodyAttachmentId;
},

// Once we have sent the body we can delete the temporary attachment
deleteBody: function() {
var attachment = new GlideSysAttachment();
attachment.deleteAttachment(this.bodyAttachmentId);

gs.log("kashyap attachID" + this.bodyAttachmentId);
},


///// UTIL FUNCTIONS

_getBoundaryId: function() {

var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

for (var i = 0; i < 8; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));

return text;


},

_getAttachmentGR: function(attachmentSysId) {
var grSysAttachment = new GlideRecord("sys_attachment");
if (grSysAttachment.get(attachmentSysId)) {

return grSysAttachment;

}
return false;
},

type: 'MultipartHelper'
};

 

 

In your scripted API :

 

Make this change in your scripted API:

 

find_real_file.png