@isaac-platform/isaac-node-red
Version:
Set of Node-RED nodes to communicate with an ISAAC system
676 lines (641 loc) • 26.9 kB
HTML
<!--
Copyright 2013, 2016 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/javascript">
const headerTypes = [
{ value: 'Accept', label: 'Accept', hasValue: false },
{ value: 'Accept-Encoding', label: 'Accept-Encoding', hasValue: false },
{ value: 'Accept-Language', label: 'Accept-Language', hasValue: false },
{ value: 'Authorization', label: 'Authorization', hasValue: false },
{ value: 'Content-Type', label: 'Content-Type', hasValue: false },
{ value: 'Cache-Control', label: 'Cache-Control', hasValue: false },
{ value: 'User-Agent', label: 'User-Agent', hasValue: false },
{ value: 'Location', label: 'Location', hasValue: false },
{
value: 'other',
label: 'other',
hasValue: true,
icon: 'red/images/typedInput/az.svg',
},
{ value: 'msg', label: 'msg.', hasValue: true },
];
const headerOptions = {};
const defaultOptions = [
{
value: 'other',
label: 'other',
hasValue: true,
icon: 'red/images/typedInput/az.svg',
},
{ value: 'msg', label: 'msg.', hasValue: true },
];
headerOptions['accept'] = [
{ value: 'text/plain', label: 'text/plain', hasValue: false },
{ value: 'text/html', label: 'text/html', hasValue: false },
{ value: 'application/json', label: 'application/json', hasValue: false },
{ value: 'application/xml', label: 'application/xml', hasValue: false },
...defaultOptions,
];
headerOptions['accept-encoding'] = [
{ value: 'gzip', label: 'gzip', hasValue: false },
{ value: 'deflate', label: 'deflate', hasValue: false },
{ value: 'compress', label: 'compress', hasValue: false },
{ value: 'br', label: 'br', hasValue: false },
{ value: 'gzip, deflate', label: 'gzip, deflate', hasValue: false },
{ value: 'gzip, deflate, br', label: 'gzip, deflate, br', hasValue: false },
...defaultOptions,
];
headerOptions['accept-language'] = [
{ value: '*', label: '*', hasValue: false },
{ value: 'en-GB, en-US, en;q=0.9', label: 'en-GB, en-US, en;q=0.9', hasValue: false },
{ value: 'de-AT, de-DE;q=0.9, en;q=0.5', label: 'de-AT, de-DE;q=0.9, en;q=0.5', hasValue: false },
{ value: 'es-mx,es,en;q=0.5', label: 'es-mx,es,en;q=0.5', hasValue: false },
{ value: 'fr-CH, fr;q=0.9, en;q=0.8', label: 'fr-CH, fr;q=0.9, en;q=0.8', hasValue: false },
{
value: 'zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6',
label: 'zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6',
hasValue: false,
},
{ value: 'ja-JP, jp', label: 'ja-JP, jp', hasValue: false },
...defaultOptions,
];
headerOptions['content-type'] = [
{ value: 'text/css', label: 'text/css', hasValue: false },
{ value: 'text/plain', label: 'text/plain', hasValue: false },
{ value: 'text/html', label: 'text/html', hasValue: false },
{ value: 'application/json', label: 'application/json', hasValue: false },
{ value: 'application/octet-stream', label: 'application/octet-stream', hasValue: false },
{ value: 'application/pdf', label: 'application/pdf', hasValue: false },
{ value: 'application/xml', label: 'application/xml', hasValue: false },
{ value: 'application/zip', label: 'application/zip', hasValue: false },
{ value: 'multipart/form-data', label: 'multipart/form-data', hasValue: false },
{ value: 'audio/aac', label: 'audio/aac', hasValue: false },
{ value: 'audio/ac3', label: 'audio/ac3', hasValue: false },
{ value: 'audio/basic', label: 'audio/basic', hasValue: false },
{ value: 'audio/mp4', label: 'audio/mp4', hasValue: false },
{ value: 'audio/ogg', label: 'audio/ogg', hasValue: false },
{ value: 'image/bmp', label: 'image/bmp', hasValue: false },
{ value: 'image/gif', label: 'image/gif', hasValue: false },
{ value: 'image/jpeg', label: 'image/jpeg', hasValue: false },
{ value: 'image/png', label: 'image/png', hasValue: false },
{ value: 'image/tiff', label: 'image/tiff', hasValue: false },
...defaultOptions,
];
headerOptions['cache-control'] = [
{ value: 'max-age=0', label: 'max-age=0', hasValue: false },
{ value: 'max-age=86400', label: 'max-age=86400', hasValue: false },
{ value: 'no-cache', label: 'no-cache', hasValue: false },
...defaultOptions,
];
headerOptions['user-agent'] = [{ value: 'Mozilla/5.0', label: 'Mozilla/5.0', hasValue: false }, ...defaultOptions];
function getHeaderOptions(headerName) {
const lc = (headerName || '').toLowerCase();
let opts = headerOptions[lc];
return opts || defaultOptions;
}
// isaac prefix ensures that node appears when filtering by "isaac"
RED.nodes.registerType('isaac generic request', {
category: 'ISAAC',
color: '#C0C0C0',
icon: 'isaac.svg',
inputs: 1,
outputs: 1,
paletteLabel: 'generic request',
label: function () {
if (this.name) {
return this.name;
}
return this.paletteLabel;
},
defaults: {
isaacConnection: { type: 'isaac-connection', required: true },
name: { value: '' },
method: { value: 'GET' },
ret: { value: 'txt' },
paytoqs: { value: false },
url: {
value: '',
validate: function (v, opt) {
if (v.trim().length === 0 || v.indexOf('://') === -1 || v.trim().indexOf('http') === 0) {
return true;
}
return 'Invalid url';
},
},
tls: { type: 'tls-config', required: false, label: 'TLS Configuration' },
persist: { value: false },
proxy: { type: 'http proxy', required: false, label: 'Proxy Configuration' },
insecureHTTPParser: { value: false },
authType: { value: '' },
senderr: { value: false },
headers: { value: [] },
},
credentials: {
user: { type: 'text' },
password: { type: 'password' },
},
oneditprepare: function () {
const updateApiLink = () => {
const configNodeId = $('#node-input-isaacConnection').val();
if (!configNodeId) {
return;
}
const configNode = RED.nodes.node(configNodeId);
if (!configNode || !configNode.ipAddress) {
const links = document.querySelectorAll('.isaac-workspace-link, .isaac-doc-link');
links.forEach((link) => {
link.style.display = 'none';
});
return;
}
const docLink = document.querySelector('.isaac-doc-link');
if (docLink) {
docLink.style.display = docLink.classList.contains('isaac-full-width') ? 'block' : 'inline';
docLink.href = `${configNode.ipAddress}/docs/v1`;
}
};
updateApiLink();
$('#node-input-isaacConnection').change(updateApiLink);
const node = this;
$('#node-input-useAuth').on('change', function () {
if ($(this).is(':checked')) {
$('.node-input-useAuth-row').show();
// Nodes (< version 0.20.x) with credentials but without authentication type, need type 'basic'
if (!$('#node-input-authType').val()) {
$('#node-input-authType-select').val('basic').trigger('change');
}
} else {
$('.node-input-useAuth-row').hide();
$('#node-input-authType').val('');
$('#node-input-user').val('');
$('#node-input-password').val('');
}
RED.tray.resize();
});
$('#node-input-authType-select').on('change', function () {
const val = $(this).val();
$('#node-input-authType').val(val);
if (val === 'basic' || val === 'digest') {
$('.node-input-basic-row').show();
$('#node-span-password').show();
$('#node-span-token').hide();
} else if (val === 'bearer') {
$('.node-input-basic-row').hide();
$('#node-span-password').hide();
$('#node-span-token').show();
$('#node-input-user').val('');
}
RED.tray.resize();
});
$('#node-input-method').on('change', function () {
if ($(this).val() == 'GET') {
$('.node-input-paytoqs-row').show();
} else {
$('.node-input-paytoqs-row').hide();
}
RED.tray.resize();
});
if (node.paytoqs === true || node.paytoqs == 'query') {
$('#node-input-paytoqs').val('query');
} else if (node.paytoqs === 'body') {
$('#node-input-paytoqs').val('body');
} else {
$('#node-input-paytoqs').val('ignore');
}
if (node.authType) {
$('#node-input-useAuth').prop('checked', true);
$('#node-input-authType-select').val(node.authType);
$('#node-input-authType-select').change();
} else {
$('#node-input-useAuth').prop('checked', false);
}
$('#node-input-useAuth').change();
function updateTLSOptions() {
if ($('#node-input-usetls').is(':checked')) {
$('#node-row-tls').show();
} else {
$('#node-row-tls').hide();
}
RED.tray.resize();
}
if (node.tls) {
$('#node-input-usetls').prop('checked', true);
} else {
$('#node-input-usetls').prop('checked', false);
}
updateTLSOptions();
$('#node-input-usetls').on('click', function () {
updateTLSOptions();
});
function updateProxyOptions() {
if ($('#node-input-useProxy').is(':checked')) {
$('#node-input-useProxy-row').show();
} else {
$('#node-input-useProxy-row').hide();
}
RED.tray.resize();
}
if (node.proxy) {
$('#node-input-useProxy').prop('checked', true);
} else {
$('#node-input-useProxy').prop('checked', false);
}
if (node.insecureHTTPParser) {
$('node-intput-insecureHTTPParser').prop('checked', true);
} else {
$('node-intput-insecureHTTPParser').prop('checked', false);
}
updateProxyOptions();
$('#node-input-useProxy').on('click', function () {
updateProxyOptions();
});
$('#node-input-ret').on('change', function () {
if ($('#node-input-ret').val() === 'obj') {
$('#tip-json').show();
} else {
$('#tip-json').hide();
}
RED.tray.resize();
});
const hasMatch = function (arr, value) {
return arr.some(function (ht) {
return ht.value === value;
});
};
const headerList = $('#node-input-headers-container')
.css('min-height', '150px')
.css('min-width', '450px')
.editableList({
addItem: function (container, i, header) {
const row = $('<div/>')
.css({
overflow: 'hidden',
whiteSpace: 'nowrap',
display: 'flex',
})
.appendTo(container);
const propertNameCell = $('<div/>').css({ 'flex-grow': 1 }).appendTo(row);
const propertyName = $('<input/>', { class: 'node-input-header-name', type: 'text', style: 'width: 100%' })
.appendTo(propertNameCell)
.typedInput({ types: headerTypes });
const propertyValueCell = $('<div/>').css({ 'flex-grow': 1, 'margin-left': '10px' }).appendTo(row);
const propertyValue = $('<input/>', {
class: 'node-input-header-value',
type: 'text',
style: 'width: 100%',
})
.appendTo(propertyValueCell)
.typedInput({
types: getHeaderOptions(header.keyType),
});
const setup = function (_header) {
const headerTypeIsAPreset = function (h) {
return hasMatch(headerTypes, h);
};
const headerValueIsAPreset = function (h, v) {
return hasMatch(getHeaderOptions(h), v);
};
const { keyType, keyValue, valueType, valueValue } = header;
if (keyType == 'msg' || keyType == 'other') {
propertyName.typedInput('type', keyType);
propertyName.typedInput('value', keyValue);
} else if (headerTypeIsAPreset(keyType)) {
propertyName.typedInput('type', keyType);
} else {
propertyName.typedInput('type', 'other');
propertyName.typedInput('value', keyValue);
}
if (valueType == 'msg' || valueType == 'other') {
propertyValue.typedInput('type', valueType);
propertyValue.typedInput('value', valueValue);
} else if (headerValueIsAPreset(propertyName.typedInput('type'), valueType)) {
propertyValue.typedInput('type', valueType);
} else {
propertyValue.typedInput('type', 'other');
propertyValue.typedInput('value', valueValue);
}
};
setup(header);
propertyName.on('change', function (event) {
propertyValue.typedInput('types', getHeaderOptions(propertyName.typedInput('type')));
});
},
sortable: true,
removable: true,
});
if (node.headers) {
for (let index = 0; index < node.headers.length; index++) {
const element = node.headers[index];
headerList.editableList('addItem', node.headers[index]);
}
}
},
oneditsave: function () {
if (!$('#node-input-usetls').is(':checked')) {
$('#node-input-tls').val('_ADD_');
}
if (!$('#node-input-useProxy').is(':checked')) {
$('#node-input-proxy').val('_ADD_');
}
const headers = $('#node-input-headers-container').editableList('items');
const node = this;
node.headers = [];
headers.each(function (i) {
const header = $(this);
const keyType = header.find('.node-input-header-name').typedInput('type');
const keyValue = header.find('.node-input-header-name').typedInput('value');
const valueType = header.find('.node-input-header-value').typedInput('type');
const valueValue = header.find('.node-input-header-value').typedInput('value');
if (keyType !== '' || keyType === 'other' || keyType === 'msg') {
node.headers.push({
keyType,
keyValue,
valueType,
valueValue,
});
}
});
},
oneditresize: function (size) {
const dlg = $('#dialog-form');
const expandRow = dlg.find('.node-input-headers-container-row');
let height = dlg.height() - 5;
if (expandRow && expandRow.length) {
const siblingRows = dlg.find('> .form-row:not(.node-input-headers-container-row)');
for (let i = 0; i < siblingRows.size(); i++) {
const cr = $(siblingRows[i]);
if (cr.is(':visible')) height -= cr.outerHeight(true);
}
$('#node-input-headers-container').editableList('height', height);
}
},
});
</script>
<script type="text/html" data-template-name="isaac generic request">
<div class="form-row">
<label for="node-input-isaacConnection"><i class="fa fa-server"></i> Connection</label>
<input type="text" id="node-input-isaacConnection" />
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span>Name</span></label>
<input type="text" id="node-input-name" />
</div>
<div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> <span>Method</span></label>
<select type="text" id="node-input-method" style="width:70%;">
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
<option value="HEAD">HEAD</option>
<option value="use">- set by msg.method -</option>
</select>
</div>
<div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> <span>URL</span></label>
<input id="node-input-url" type="text" placeholder="Path after /v1/api/" />
</div>
<div class="form-row node-input-paytoqs-row">
<label for="node-input-paytoqs"><span>Payload</span></label>
<select id="node-input-paytoqs" style="width: 70%;">
<option value="ignore">Ignore</option>
<option value="query">Query</option>
<option value="body">Body</option>
</select>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-usetls" style="display: inline-block; width: auto; vertical-align: top;" />
<label for="node-input-usetls" style="width: auto">Enable secure (SSL/TLS) connection</label>
<div id="node-row-tls" class="hide">
<label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-input-tls"
><span>TLS Configuration</span></label
><input type="text" style="width: 300px" id="node-input-tls" />
</div>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;" />
<label for="node-input-useAuth" style="width: 70%;"><span>Use authentication</span></label>
<div style="margin-left: 20px" class="node-input-useAuth-row hide">
<div class="form-row">
<label for="node-input-authType-select"><i class="fa fa-user-secret "></i> <span>Type</span></label>
<select type="text" id="node-input-authType-select" style="width:70%;">
<option value="basic">basic authentication</option>
<option value="digest">digest authentication</option>
<option value="bearer">bearer authentication</option>
</select>
<input type="hidden" id="node-input-authType" />
</div>
<div class="form-row node-input-basic-row">
<label for="node-input-user"><i class="fa fa-user"></i> <span>Username</span></label>
<input type="text" id="node-input-user" />
</div>
<div class="form-row">
<label for="node-input-password">
<i class="fa fa-lock"></i> <span id="node-span-password">Password</span
><span id="node-span-token" style="display:none">Token</span></label
>
<input type="password" id="node-input-password" />
</div>
</div>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-persist" style="display: inline-block; width: auto; vertical-align: top;" />
<label for="node-input-persist" style="width: auto">Enable connection keep-alive</label>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-useProxy" style="display: inline-block; width: auto; vertical-align: top;" />
<label for="node-input-useProxy" style="width: auto;"><span>Use proxy</span></label>
<div id="node-input-useProxy-row" class="hide">
<label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-input-proxy"
><i class="fa fa-globe"></i> <span>Proxy Configuration</span></label
><input type="text" style="width: 270px" id="node-input-proxy" />
</div>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-senderr" style="display: inline-block; width: auto; vertical-align: top;" />
<label for="node-input-senderr" style="width: auto">Only send non-2xx responses to Catch node</label>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-insecureHTTPParser"
style="display: inline-block; width: auto; vertical-align: top;"
/>
<label for="node-input-insecureHTTPParser" , style="width: auto;">Disable strict HTTP parsing</label>
</div>
<div class="form-row">
<label for="node-input-ret"><i class="fa fa-arrow-left"></i> <span>Return</span></label>
<select type="text" id="node-input-ret" style="width:70%;">
<option value="txt">a UTF-8 string</option>
<option value="bin">a binary buffer</option>
<option value="obj">a parsed JSON object</option>
</select>
</div>
<div
style="color:#A3A3A3; display:flex; gap:16px; align-items:center; padding:8px 16px; border: 1px solid #a3a3a3; border-radius:12px; margin-bottom:12px"
>
<i class="fa fa-info" style="width:16px; text-align:center;"></i>
<p style="margin: 0; padding: 0;">If the JSON parse fails the fetched string is returned as-is.</p>
</div>
<div class="form-row" style="margin-bottom:0;">
<label><i class="fa fa-list"></i> <span>Headers</span></label>
</div>
<div class="form-row node-input-headers-container-row">
<ol id="node-input-headers-container"></ol>
</div>
<div
style="color:#A3A3A3; display:flex; gap:16px; align-items:center; padding:8px 16px; border: 1px solid #a3a3a3; border-radius:12px"
>
<i class="fa fa-info" style="width:16px; text-align:center;"></i>
<p style="margin: 0; padding: 0;">
The ISAAC token specified in the connection configuration node will be applied automatically.
</p>
</div>
<div
style="color:#A3A3A3; display:flex; gap:16px; align-items:center; padding:8px 16px; border: 1px solid #a3a3a3; border-radius:12px; margin-top:12px"
>
<i class="fa fa-info" style="width:16px; text-align:center;"></i>
<p style="margin: 0; padding: 0;">
All properties can be overwritten by incoming messages.<br />
Hover a property label to see how it can be overwritten.<br />
Required properties (*) can be provided either in the UI or in messages.
</p>
</div>
<div style="margin-top:12px">
<a href="" target="_blank" class="isaac-doc-link isaac-full-width" style="text-decoration:underline">
<i class="fa fa-book" style="margin-right:8px"></i>API Documentation
</a>
</div>
</script>
<script type="text/html" data-help-name="isaac generic request">
<p>
Sends HTTP requests to ISAAC and returns the response. This node is forked from the built-in
<code>http request</code> node so its usage is largerly the same, except that it automatically provides the URL to
ISAAC at the beginning of URLs based on the server configuration node.
</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt class="optional">url <span class="property-type">string</span></dt>
<dd>
If not configured in the node, this optional property sets the url of the request. The node automatically provides
the ISAAC host based on its configuration node and appends <code>/api/v1/</code>. You only need to specify the
path and any query parameters after this part. For example: enter <code>users</code> to target
<code>http://{host}/api/v1/users</code>
</dd>
<dt class="optional">method <span class="property-type">string</span></dt>
<dd>
If not configured in the node, this optional property sets the HTTP method of the request. Must be one of
<code>GET</code>, <code>PUT</code>, <code>POST</code>, <code>PATCH</code> or <code>DELETE</code>.
</dd>
<dt class="optional">headers <span class="property-type">object</span></dt>
<dd>
Sets the HTTP headers of the request. NOTE: Any headers set in the node configuration will overwrite any matching
headers in <code>msg.headers</code>
</dd>
<dt class="optional">cookies <span class="property-type">object</span></dt>
<dd>If set, can be used to send cookies with the request.</dd>
<dt class="optional">payload</dt>
<dd>Sent as the body of the request.</dd>
<dt class="optional">rejectUnauthorized</dt>
<dd>If set to <code>false</code>, allows requests to be made to https sites that use self signed certificates.</dd>
<dt class="optional">followRedirects</dt>
<dd>If set to <code>false</code> prevent following Redirect (HTTP 301).<code>true</code> by default</dd>
<dt class="optional">requestTimeout</dt>
<dd>
If set to a positive number of milliseconds, will override the globally set
<code>httpRequestTimeout</code> parameter.
</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">string | object | buffer</span></dt>
<dd>
The body of the response. The node can be configured to return the body as a string, attempt to parse it as a JSON
string or leave it as a binary buffer.
</dd>
<dt>statusCode <span class="property-type">number</span></dt>
<dd>The status code of the response, or the error code if the request could not be completed.</dd>
<dt>headers <span class="property-type">object</span></dt>
<dd>An object containing the response headers.</dd>
<dt>responseUrl <span class="property-type">string</span></dt>
<dd>
In case any redirects occurred while processing the request, this property is the final redirected url. Otherwise,
the url of the original request.
</dd>
<dt>responseCookies <span class="property-type">object</span></dt>
<dd>If the response includes cookies, this property is an object of name/value pairs for each cookie.</dd>
<dt>redirectList <span class="property-type">array</span></dt>
<dd>
If the request was redirected one or more times, the accumulated information will be added to this property.
`location` is the next redirect destination. `cookies` is the cookies returned from the redirect source.
</dd>
</dl>
<h3>Details</h3>
<p>
When configured within the node, the URL property can contain
<a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache-style</a> tags. These allow the url to
be constructed using values of the incoming message. For example, if the url is set to
<code>example.com/{{{topic}}}</code>, it will have the value of <code>msg.topic</code> automatically inserted. Using
{{{...}}} prevents mustache from escaping characters like / & etc.
</p>
<p>
The node can optionally automatically encode <code>msg.payload</code> as query string parameters for a GET request,
in which case <code>msg.payload</code> has to be an object.
</p>
<p>
<b>Note</b>: If running behind a proxy, the standard <code>http_proxy=...</code> environment variable should be set
and Node-RED restarted, or use Proxy Configuration. If Proxy Configuration was set, the configuration take
precedence over environment variable.
</p>
<h4>Using multiple HTTP Request nodes</h4>
<p>
In order to use more than one of these nodes in the same flow, care must be taken with the
<code>msg.headers</code> property. The first node will set this property with the response headers. The next node
will then use those headers for its request - this is not usually the right thing to do. If
<code>msg.headers</code> property is left unchanged between nodes, it will be ignored by the second node. To set
custom headers, <code>msg.headers</code> should first be deleted or reset to an empty object: <code>{}</code>.
</p>
<h4>Cookie handling</h4>
<p>
The <code>cookies</code> property passed to the node must be an object of name/value pairs. The value can be either
a string to set the value of the cookie or it can be an object with a single <code>value</code> property.
</p>
<p></p>
<p>Any cookies returned by the request are passed back under the <code>responseCookies</code> property.</p>
<h4>Content type handling</h4>
<p>
If <code>msg.payload</code> is an Object, the node will automatically set the content type of the request to
<code>application/json</code> and encode the body as such.
</p>
<p>
To encode the request as form data, <code>msg.headers["content-type"]</code> should be set to
<code>application/x-www-form-urlencoded</code>.
</p>
<h4>File Upload</h4>
<p>
To perform a file upload, <code>msg.headers["content-type"]</code> should be set to
<code>multipart/form-data</code> and the <code>msg.payload</code> passed to the node must be an object with the
following structure:
</p>
<pre><code>{
"KEY": {
"value": FILE_CONTENTS,
"options": {
"filename": "FILENAME"
}
}
}</code></pre>
<p>
The values of <code>KEY</code>, <code>FILE_CONTENTS</code> and <code>FILENAME</code> should be set to the
appropriate values.
</p>
</script>