Tutorial: Web Request

Web Request

Currently web requests are done using our net.Curl classes including net.Curl.Easy and net.Curl.Multi and we know you would like to control everything but we just make it simpler by using a request.js similar library below.

In the JS Object, use the follow code:

const request = require('./request-0.0.3.js');
// Send the get request
request.get({
        url: 'http://date.jsontest.com/'
    }, 
    function (error, response, body) {
        console.log("error:", error);
        console.log("response:", response);
        console.log("body:", body);
    }
);

The request-0.0.3.js (Download) to be added to JS Resource.

// Changelogs:
// 0.0.3 (2022-07-27):
// - Added methods for PUT, PATCH, DELETE
// - Move shared codes into _perform_single_easy
// 0.0.2:
// - Set onmessage to null to release unused web connections
// 0.0.1:
// - First release

var request = {
    post: requestPost,
    get: requestGet,
    put: requestPut,
    patch: requestPatch,
    delete: requestDelete,
    version: "0.0.3",
};

/**
 * Make HTTP GET request
 * @param {Object}   opt                - options for get request. example: `{url: 'http://www.google.com'}`
 * @param {string}   opt.url            - target url
 * @param {string}   opt.useragent      - useragent to use in the header
 * @param {Boolean}  opt.followlocation - follow redirect responses
 * @param {Array.Object} opt.header     - headers to use in the request. example: `{"Content-Type": "application/json"}`
 * @param {function(string,object,string)} cb - callback for any results. example: `function (error, info, body) {}`
 */
function requestGet(opt, cb) {
    var easy = new net.Curl.Easy();
    var data = opt.data || {};

    var url = opt.url || "";
    url = url.replace(/^[\s\uFEFF\xA0\0]+|[\s\uFEFF\xA0\0]+$/g, ""); // polyfill of trim() plus removing \0
    var qsPos = url.indexOf('?');
    // extract existing query string from url
    // e.g. "http://localhost:8080/q?a=1&b=2" into {"a":1,"b":2} and then adde.
    if (qsPos > -1) {
        var dataInUrl = QueryString.parse(url.substring(qsPos + 1));
        // existing data has higher precedence than ones in url
        Object.assign(dataInUrl, data);
        // reassign to data
        data = dataInUrl
        // cleanup url
        url = url.substring(0, qsPos);
    }
    var qs = QueryString.stringify(data);
    if (qs.length > 0) {
        url += "?" + QueryString.stringify(data);
    }
    easy.setOpt(net.Curl.Easy.option.URL, url);

    easy.setOpt(net.Curl.Easy.option.HTTPGET, true);
    _perform_single_easy(easy, opt, cb)
}

/**
 * Make HTTP POST request
 * @param {Object}   opt                - options for get request. example: `{url: 'http://www.google.com'}`
 * @param {string}   opt.url            - target url
 * @param {string}   opt.useragent      - useragent to use in the header
 * @param {Boolean}  opt.followlocation - follow redirect responses
 * @param {Array.Object} opt.header     - headers to use in the request. example: `{"Content-Type": "application/json"}`
 * @param {Object}   opt.form           - form to use in the request. example: `{"key": "value"}`
 * @param {String}   opt.data           - string to send as the request body. example: `"{\"value\":10,\"ts\":\"2020-12-10T00:00:00Z\"}"`. This overwrites opt.form.
 * @param {function(string,object,string)} cb - callback for any results. example: `function (error, info, body) {}`
 */
function requestPost(opt, cb) {
    var easy = new net.Curl.Easy();

    var url = opt.url || "";
    url = url.replace(/^[\s\uFEFF\xA0\0]+|[\s\uFEFF\xA0\0]+$/g, ""); // polyfill of trim() plus removing \0
    easy.setOpt(net.Curl.Easy.option.URL, url);

    var requestBody = "";
    if (opt.data) {
        requestBody = opt.data;
    } else if (opt.form) {
        requestBody = QueryString.stringify(opt.form);
    }
    easy.setOpt(net.Curl.Easy.option.POSTFIELDS, requestBody);

    _perform_single_easy(easy, opt, cb)
}

/**
 * Make HTTP PUT request
 * @param {Object}   opt                      - options for get request. example: `{url: 'http://www.google.com'}`
 * @param {string}   opt.url                  - target url
 * @param {string}   opt.useragent            - useragent to use in the header
 * @param {Boolean}  opt.followlocation       - follow redirect responses
 * @param {Array.Object} opt.header           - headers to use in the request. example: `{"Content-Type": "application/json"}`
 * @param {Object}   opt.form                 - form to use in the request. example: `{"key": "value"}`
 * @param {String}   opt.data                 - string to send as the request body. example: `"{\"value\":10,\"ts\":\"2020-12-10T00:00:00Z\"}"`. This overwrites opt.form.
 * @param {function(string,object,string)} cb - callback for any results. example: `function (error, info, body) {}`
 */
function requestPut(opt, cb) {
    opt.customrequest = "PUT";
    _request_misc_method(opt, cb)
}

function requestPatch(opt, cb) {
    opt.customrequest = "PATCH";
    _request_misc_method(opt, cb)
}

function requestDelete(opt, cb) {
    opt.customrequest = "DELETE";
    _request_misc_method(opt, cb)
}

function _request_misc_method(opt, cb) {
    var easy = new net.Curl.Easy();

    var url = opt.url || "";
    url = url.replace(/^[\s\uFEFF\xA0\0]+|[\s\uFEFF\xA0\0]+$/g, ""); // polyfill of trim() plus removing \0
    easy.setOpt(net.Curl.Easy.option.URL, url);

    var requestBody = null;
    if (opt.data) {
        requestBody = opt.data;
    } else if (opt.form) {
        requestBody = QueryString.stringify(opt.form);
    }
    if (requestBody !== null) {
        easy.setOpt(net.Curl.Easy.option.POSTFIELDS, requestBody);
    }

    if (opt.customrequest !== undefined) {
        easy.setOpt(net.Curl.Easy.option.CUSTOMREQUEST, opt.customrequest);
    }

    _perform_single_easy(easy, opt, cb)
}

function _perform_single_easy(easy, opt, cb) {
    const decoder = new TextDecoder('utf-8');

    if (typeof cb !== 'function') {
        return;
    }
    var responseData = "";
    var useragent = opt.useragent || "curl/7";
    // If it is a 300 response, follow the redirection
    var followLocation = opt.followlocation || true;
    // On HMI we do not have CA root certificate chains, so we will not verify the certificate
    var sslVerifypeer = opt.ssl_verifypeer || false;


    // If it is a 300 response, follow the redirection
    easy.setOpt(net.Curl.Easy.option.FOLLOWLOCATION, followLocation);
    easy.setOpt(net.Curl.Easy.option.USERAGENT, useragent);
    // On HMI we do not have CA root certificate chains, so we will not verify the certificate
    easy.setOpt(net.Curl.Easy.option.SSL_VERIFYPEER, sslVerifypeer);
    if (opt.header) {
        var headerList = [];
        for (var key in opt.header) {
            headerList.push(key + ": " + opt.header[key]);
        }
        easy.setOpt(net.Curl.Easy.option.HTTPHEADER, headerList);
    }

    easy.setOpt(net.Curl.Easy.option.WRITEFUNCTION, function (buf) {
        var resp = decoder.decode(buf);
        responseData += resp;
    });

    var multi = new net.Curl.Multi();
    multi.onMessage((easyHandle, result) => {
        var error = net.Curl.Easy.strError(result);
        var info = {
            href: easyHandle.getInfo(net.Curl.info.EFFECTIVE_URL),
            statusCode: easyHandle.getInfo(net.Curl.info.RESPONSE_CODE),
            totalTime: easyHandle.getInfo(net.Curl.info.TOTAL_TIME),
            connectTime: easyHandle.getInfo(net.Curl.info.CONNECT_TIME),
            contentType: easyHandle.getInfo(net.Curl.info.CONTENT_TYPE),
            localIP: easyHandle.getInfo(net.Curl.info.LOCAL_IP),
            localPort: easyHandle.getInfo(net.Curl.info.LOCAL_PORT),
            requestSize: easyHandle.getInfo(net.Curl.info.REQUEST_SIZE),
        };
        var body = responseData;
        multi.removeHandle(easyHandle);
        multi.onMessage(null);
        cb(error, info, body);
    });

    multi.addHandle(easy);
    return;
}

// QueryString modified from https://github.com/nodejs/node/blob/3bdcbdb1a085b35a3a50112a51781b31d8814294/lib/querystring.js
const QueryString = { unescape: qsUnescape, escape: qsEscape, stringify: stringify, encode: stringify, parse: parse, decode: parse }; function ParsedQueryString() { } function qsUnescape(e) { return decodeURIComponent(e) } ParsedQueryString.prototype = Object.create(null); const hexTable = []; for (var i = 0; i < 256; ++i)hexTable[i] = "%" + ((i < 16 ? "0" : "") + i.toString(16)).toUpperCase(); const noEscape = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0]; function qsEscape(e) { "string" != typeof e && ("object" == typeof e ? e = String(e) : e += ""); for (var t = "", n = 0, r = 0; r < e.length; ++r) { var i = e.charCodeAt(r); if (i < 128) { if (1 === noEscape[i]) continue; n < r && (t += e.slice(n, r)), n = r + 1, t += hexTable[i] } else if (n < r && (t += e.slice(n, r)), i < 2048) n = r + 1, t += hexTable[192 | i >> 6] + hexTable[128 | 63 & i]; else if (i < 55296 || i >= 57344) n = r + 1, t += hexTable[224 | i >> 12] + hexTable[128 | i >> 6 & 63] + hexTable[128 | 63 & i]; else { var o; if (!(++r < e.length)) throw new URIError("URI malformed"); o = 1023 & e.charCodeAt(r), n = r + 1, t += hexTable[240 | (i = 65536 + ((1023 & i) << 10 | o)) >> 18] + hexTable[128 | i >> 12 & 63] + hexTable[128 | i >> 6 & 63] + hexTable[128 | 63 & i] } } return 0 === n ? e : n < e.length ? t + e.slice(n) : t } function stringifyPrimitive(e) { return "string" == typeof e ? e : "number" == typeof e && isFinite(e) ? "" + e : "boolean" == typeof e ? e ? "true" : "false" : "" } function stringify(e, t, n, r) { t = t || "&", n = n || "="; var i = QueryString.escape; if (r && "function" == typeof r.encodeURIComponent && (i = r.encodeURIComponent), null !== e && "object" == typeof e) { for (var o = Object.keys(e), s = o.length, c = s - 1, a = "", f = 0; f < s; ++f) { var l = o[f], d = e[l], h = i(stringifyPrimitive(l)) + n; if (Array.isArray(d)) { for (var p = d.length, u = p - 1, g = 0; g < p; ++g)a += h + i(stringifyPrimitive(d[g])), g < u && (a += t); p && f < c && (a += t) } else a += h + i(stringifyPrimitive(d)), f < c && (a += t) } return a } return "" } function charCodes(e) { if (0 === e.length) return []; if (1 === e.length) return [e.charCodeAt(0)]; const t = []; for (var n = 0; n < e.length; ++n)t[t.length] = e.charCodeAt(n); return t } const defSepCodes = [38], defEqCodes = [61]; function parse(e, t, n, r) { const i = new ParsedQueryString; if ("string" != typeof e || 0 === e.length) return i; var o = t ? charCodes(t + "") : defSepCodes, s = n ? charCodes(n + "") : defEqCodes; const c = o.length, a = s.length; var f = 1e3; r && "number" == typeof r.maxKeys && (f = r.maxKeys > 0 ? r.maxKeys : -1); var l = QueryString.unescape; r && "function" == typeof r.decodeURIComponent && (l = r.decodeURIComponent); const d = l !== qsUnescape, h = []; for (var p = 0, u = 0, g = 0, y = "", b = "", C = d, v = d, x = 0, m = 0; m < e.length; ++m) { const t = e.charCodeAt(m); if (t !== o[u]) { if (u = 0, v || (37 === t ? x = 1 : x > 0 && (t >= 48 && t <= 57 || t >= 65 && t <= 70 || t >= 97 && t <= 102) ? 3 == ++x && (v = !0) : x = 0), g < a) { if (t === s[g]) { if (++g === a) { const t = m - g + 1; p < t && (y += e.slice(p, t)), x = 0, p = m + 1 } continue } g = 0, C || (37 === t ? x = 1 : x > 0 && (t >= 48 && t <= 57 || t >= 65 && t <= 70 || t >= 97 && t <= 102) ? 3 == ++x && (C = !0) : x = 0) } 43 === t && (g < a ? (p < m && (y += e.slice(p, m)), y += "%20", C = !0) : (p < m && (b += e.slice(p, m)), b += "%20", v = !0), p = m + 1) } else if (++u === c) { const t = m - u + 1; if (g < a ? p < t && (y += e.slice(p, t)) : p < t && (b += e.slice(p, t)), C && (y = decodeStr(y, l)), v && (b = decodeStr(b, l)), -1 === h.indexOf(y)) i[y] = b, h[h.length] = y; else { const e = i[y]; e.pop ? e[e.length] = b : i[y] = [e, b] } if (0 == --f) break; C = v = d, x = 0, y = b = "", p = m + 1, u = g = 0 } } if (0 !== f && (p < e.length || g > 0)) if (p < e.length && (g < a ? y += e.slice(p) : u < c && (b += e.slice(p))), C && (y = decodeStr(y, l)), v && (b = decodeStr(b, l)), -1 === h.indexOf(y)) i[y] = b, h[h.length] = y; else { const e = i[y]; e.pop ? e[e.length] = b : i[y] = [e, b] } return i } function decodeStr(e, t) { try { return t(e) } catch (t) { return QueryString.unescape(e, !0) } }

module.exports = request;