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.4.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.4.js (Download) to be added to JS Resource.

// Changelogs:
// 0.0.4 (2024-08-09):
// - Use encodeURIComponent and decodeURIComponent to replace embedded QueryString module
// 0.0.3 (2022-08-05):
// - 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.4",
};

/**
 * 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 || {};

    // retrieve url from options and combine existing query string with current data
    // find existing query string
    var qsPos = opt.url.indexOf('?');
    // combine existing query string with current data
    // for example, url is "http://localhost:8080/q?a=1&b=2" and data is {"a":100,"b":2,"c":3}.
    // they will be combined as "http://localhost:8080/q?a=100&b=2&c=3"
    if (qsPos > -1) {
        var dataInUrl = parse(opt.url.substring(qsPos + 1));
        // existing data has higher precedence than ones in url
        Object.assign(dataInUrl, data);
        // reassign to data
        data = dataInUrl
        // cleanup url
        opt.url = opt.url.substring(0, qsPos);
    }
    var qs = stringify(data);
    if (qs.length > 0) {
        opt.url += "?" + stringify(data);
    }

    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) {
    _perform_single_easy_with_body(opt, cb)
}

function requestPut(opt, cb) {
    opt.customrequest = "PUT";
    _perform_single_easy_with_body(opt, cb)
}

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

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

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

    // set the customerequest option (PUT/PATCH/DELETE)
    if (opt.customrequest !== undefined) {
        easy.setOpt(net.Curl.Easy.option.CUSTOMREQUEST, opt.customrequest);
    }

    // put/patch/delete may or may not contain body
    var requestBody = null;
    if (opt.data !== undefined) {
        requestBody = opt.data;
    } else if (opt.form !== undefined) {
        requestBody = stringify(opt.form);
    }
    if (requestBody !== null) {
        easy.setOpt(net.Curl.Easy.option.POSTFIELDS, requestBody);
    }

    _perform_single_easy(easy, opt, cb)
}

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

    var url = opt.url || "";
    // url may contain spaces or \0. use polyfilled trim() and remove \0 too
    url = url.replace(/^[\s\uFEFF\xA0\0]+|[\s\uFEFF\xA0\0]+$/g, "");
    easy.setOpt(net.Curl.Easy.option.URL, url);

    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;
}

// Stringify an object into a query string
function stringify(obj) {
    return Object.keys(obj)
        .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
        .join('&');
}

// Parse a query string into an object
function parse(queryString) {
    return queryString.split('&')
        .reduce((acc, pair) => {
            const [key, value] = pair.split('=').map(decodeURIComponent);
            acc[key] = value;
            return acc;
        }, {});
}

module.exports = request;