node.js同时发送多个http请求的两种方式

Posted 零君聊软件

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了node.js同时发送多个http请求的两种方式相关的知识,希望对你有一定的参考价值。

最近为了实现一个kibana的plugin,与node.js斗争了一段时间。过程中遇到了一些有意思的编程问题,本文分享其中一个node.js同时发送多个http请求的问题。


node.js中的I/O操作都是采用的异步机制,通过回调返回处理结果。例如:

request({

    url: "http://your.service.url/res",

    method: "POST",

    headers: {'Content-Type': 'application/json'},

    body: jsonObj

}, function (error, response, body){

    console.log(response);

});

/* The following code shouldn't depend on data returned by the callback.  Actually there shouldn't an any code in the following at all. All following code should be included in the body of the callback function. */


这里要注意的是,request调用会马上返回,而callback函数要等I/O 结束后才会被调用。所以request后面的语句不能依赖于callback返回的任何数据。通常后面也不应该有任何其他代码,而是将后续的处理放在callback中执行。


但是如果同时要发送多个HTTP请求,而且后续的处理依赖于多个请求返回的数据,那么情况就变得稍微复杂,自然也更有意思了。下面以一个假想的例子来说明。


假设有一个图书借阅系统,每个用户都可以在线自助借阅图书。该系统针对不同的人设置了可以借阅的图书的列表。每个用户每次可以借阅多本书。 当一个用户选择好了多本书,提交申请借阅时,系统会审核该用户的权限,查看该用户是否有权借阅选择的图书。只有当该用户有权借阅所有选择的图书时,该申请才会通过;换句话说,如果该用户无权借阅选择的图书中任意一本,该申请就会被拒绝。


假设用户alice选择了3本书,分别是book1、book2、book3,当她提交借阅申请时,后端服务器会接收到类似下面的申请数据:

const reqContexts = [

    {

        subject: "user:alice",

        resource: "/book/book1",

        action: "borrow"

    },

    {

        subject: "user:alice",

        resource: "/book/book2",

        action: "borrow"

    },

    {

        subject: "user:alice",

        resource: "/book/book3",

        action: "borrow"

    }

]; 


这时服务器就要通过REST API与authorization service进行通信,来检查用户alice是否有权借阅这三本书。这时,可以通过同步或异步的方式向authorization service发送REST请求。

注:可查阅我以前写的一篇文章“应用程序安全(Application Security)”,来了解更多authentication和authorization的概念。


同步方式

同步方式就是串行发送REST请求,依次检查用户是否有权借阅每本书,如果没有权限,则用户的借阅申请被拒绝,后续的向authorization service的REST请求也没必要进行了;否则继续下一次REST请求。实现代码大致如下。

import httpRequest from 'request';


export default function (reqContexts, callback) {

    if (reqContexts === null || reqContexts.length === 0) {

        callback(true);

    } else {

        const evaluationPermission = function (index) {

            const options = {

                url: 'http://authorization.service.url:8088/permission-check/v1',

                method: 'POST',

                headers: {'Content-Type': 'application/json'},

                json: reqContexts[index]

            };              

            evaluate(options, function (result) {

                // We can't callback until all evaluations finish or anyone is denied.

                if (index === reqContexts.length-1 || !result) {

                    callback(result);

                } else {

                    evaluationPermission(index+1);

                }

            });

        }; 


        evaluationPermission(0);

    }

}


function evaluate(options, callback) {

    httpRequest(options, function (error, response, body) {

        if (!error && response.statusCode === 200) {

            callback(body.allowed);

        } else {

            callback(false);

        }

    });

}


上述代码中采用了递归和闭包两种技术。常量evaluationPermission的值是一个内部函数。与authorization service的交互实现在函数evaluate中。authorization service返回的是一个JSON对象,其中的字段allowed为true时,表示用户有权借阅该书,否则无权。


同步方式的优点是减轻了authorization service的压力,因为REST请求时串行发送的,而且如果其中任意一个返回false,后续的请求就都取消了。但缺点是权限验证的过程可能会花很长时间,降低终端用户体验。


异步方式

异步方式就是并行执行REST请求。不管三七二十一,先把所有的请求发送给authorization service。实现代码大致如下。

import httpRequest from 'request';


export default function (reqContexts, callback) {

    if (reqContexts === null || reqContexts.length === 0) {

        callback(true);

    } else {

        let hasAlreadyReturned = false;

        let callbackCount = 0;

        const evaluationPermission = function () {

            for (let i=0; i<reqContexts.length; i++) {

                const options = {

                    url: 'http://your.service.url:8088/permission-check/v1',

                    method: 'POST',

                    headers: {'Content-Type': 'application/json'},

                    json: reqContexts[i]

                };                

                evaluate(options, function (result) {

                    callbackCount++;

                    

                    if (!hasAlreadyReturned) {

                        if (callbackCount === reqContexts.length || !result) {

                            hasAlreadyReturned = true;

                            callback(result);

                        } 

                    }

                });

            }

        }


        evaluationPermission();

    }

}


function evaluate(options, callback) {

    httpRequest(options, function (error, response, body) {

        if (!error && response.statusCode === 200) {

            callback(body.allowed);

        } else {

            callback(false);

        }

    });

}


异步方式同样采用了闭包的技术,所以可以访问函数内部(上下文环境中)变量的值,

    let hasAlreadyReturned = false;

    let callbackCount = 0;

变量hasAlreadyReturned用来标示是否已经通知了调用者,这样可以保证只通知调用者一次。变量callbackCount是个计数器,用来记录发送给authorization service的REST请求完成的数量。另外,值得一提的是JavaScript的执行环境是单线程的,所以回调函数被多线程同时回调执行的情况不会发生

注意:这里只是说javascript执行环境是单线程的,但底层I/O还是并发执行的。


异步方式的优点是整个权限验证的过程比较快,终端用户体验自然更好。但是缺点是对authorization service造成的负荷更大。而且当明确知道最终验证结果时,已经发送出去的REST请求也会继续进行,只是直接忽略返回的结果而已。


注意本文中的例子是虚构的,本文的重点是在于分享在node.js中使用同步或异步方式同时发送多个http请求的例子。在实际应用中,具体采用哪种方式,要具体问题具体分析,但一般来说异步方式更符合JavaScript的思想。


--END--


以上是关于node.js同时发送多个http请求的两种方式的主要内容,如果未能解决你的问题,请参考以下文章

Android HTTP访问的两种方式(HttpClient和HttpURLConnection)

node.js中函数的两种封装方式

如何在 node.js 中同时使用多个代理(代理)

在 Node.js 中的单个 HTTP 请求中调用多个 HTTP 请求

springboot集成websocket的两种实现方式

node.js接收异步任务结果的两种方法----callback和事件广播