An overview of Attachment handling in Unifi.
The mechanism for outbound attachment transmission has the following steps:
- Detect attachment creation (insert to the Attachments table) and generate Bonded Attachment (BA) records (in the Ready state)
- Identify any Message Type(s) which have attachment_added = true, and if found, initiate the message(s) immediately
- If no messages have attachment_added = true, we take no further action here; the BA records in the Ready state will be picked up by subsequent outbound message(s)
- When a message is being sent, one or more BA records will be selected for inclusion in the message and these will be set to the Sending state.
- When the transaction enters the Sending state the outbound payload will be constructed and placeholders will be added for the selected attachments; the BA records will be set to the Sent state
- Just before the request is sent, the placeholders will be substituted with the real attachment data encoded in Base64 format
The following sections describe the steps above in more detail.
The Attachments table [sys_attachment] has a business rule
[ws] Unifi attachmentswhich detects newly inserted attachments (it does not run for x_snd_eb tables). This after insert rule creates Bonded Attachment (BA) records as required by calling
For every bond that references the attachment's target record, a BA record is created in the Ready state.
If any BA records were created,
Message.processOutboundis called against the target record, with an option that indicates that this was as a result of an attachment being added.
A standard scan of matching messages is initiated (Active, Create/Update, Outbound/Bidirectional, for the target table) but also limited to only messages with attachment_added = true. If any messages are found, they are immediately triggered to be sent.
If no messages have attachment_added = true then the standard message processing will find the 'Ready' bonded attachments the next time a suitable message is triggered.
When a message is being sent, a check is made for any BA records in the Ready or Rejected states. This is performed in
AttachmentHandler#getAttachmentsToSend, called from
For each one found, the state will be set to Failed, Sending or left unchanged.
- Failed means we can never send the attachment (e.g. attachments inhibited, attachment too large, or other reason)
- Sending means we can include the attachment in this message; the BA will be linked to the current transaction
- No change means the attachment won't be sent in this message, but is still a candidate for inclusion in future messages
When a transaction enters the Sending state, the
Transaction#sendmethod will create a new request and call
Message#generatePayload. This method processes the
Stage to Request (Outbound)script, if present, and if that returns no payload then the XML template will be processed.
The XML Template of the Message can be set up to include a UI macro
<snd_eb_attachments>which finds each Bonded Attachment in the Sending state, embeds an attachment placeholder for each one into the payload, and sets the BA to the Sent state (the UI Macro is part of "[ws] Unifi Standard API" application).
This UI Macro uses
<x-attachment-data>elements. There will be one element per attachment returned and it will contain the sys_id of sys_attachment to be sent.
AttachmentSenderis essentially an iterator over all BA records for a specific transaction where the state is Sending. It marks each BA as Sent once it has been selected
- If no UI macro is included, the BA will be left in the Sending state against the current transaction but will never be included in the payload and never sent
Currently all attachments are carried as embedded Base64 data within the body of a Message. For outbound attachments, the data is injected into the payload at HttpRequest send time. The position for inclusion of attachments into the message body is marked using XML element placeholders.
The conversion of these placeholder elements is done in
HttpRequest#sendwhich calls the static method
AttachmentHandler.injectAttachments. This replaces the
<x-attachment-data>elements with Base64 data. This behaviour is hard-coded.
The same substitution is performed regardless of whether the outbound message is SOAP or REST. While the substitution string looks like an XML element, the substitution is done using regex and so we could use these strings to indicate the position of Base64 attachment data to be embedded into an outbound REST message.
Multipart form data is not naturally supported OOTB in native ServiceNow. However, we do have a way of supporting it in Unifi. You can read more about that on the following page:
Unifi processes attachments embedded in inbound messages as follows:
- The inbound request is identified as a specific Message and the
extract_attachmentsscript is called
- The script analyses the inbound payload, removes the attachment data, and creates attachments linked to the inbound HTTPRequest record
- We establish the transaction and stage and then create a Bonded Attachment record for each attachment extracted
- If processing of the inbound message succeeds (process_state is Accepted) the BA records are linked to the Bond and set to Complete, the attachments moved from the HTTPRequest record to the target record, and the bond updated to reflect the attachments in this transaction
- The newly added Bonded Attachments are copied to every other bond that the target record is associated with, to allow those bonds to send copies of the attachments to their bonded systems
The following sections describe some steps above in more detail.
snd_eb_api_utils.extractAttachmentsscript expects an XML document or string as input and searches for an element called
This is expected to contain a sequence of
eb:Attachmentelements which each have
eb:Datachild nodes. The text content of these are read and stored in a
resultobject. The text content of the Data element is removed from the input XML (so when it is passed back we can log the inbound request_payload without the attachment data).
The extracted information is passed to
AttachmentHandler.saveAttachment(with the raw Base64 data already decoded). This method creates the attachment and links it to the HTTPRequest record (note that this will not trigger the after insert rule on the Attachments table, since HTTPRequest is an x_snd_eb type table). The method is expected to return an
x-attachment-datatag containing the sys_id of the attachment created as an attribute. This tag is inserted into the original XML.
The payload is returned with
x-attachment-dataplaceholder tags substituted for the raw attachment data.
AttachmentHandler.createInboundAttachmentsscript expects to process a
x-attachment-dataplaceholders embedded within. The sys_ids of these placeholders are extracted using regex search, and for each sys_id we call
AttachmentHandler.addToTransaction, which creates a Bonded Attachment in the Ready state (There will be no Bond specified on the BA at this point).
x-attachment-dataplaceholder tags is hard-coded into Unifi.