原文: https://fullstack-developer.academy/concurrent-http-connections-in-node-js/

------------------------------------------------------------------------------------------

Browsers, as well as Node.js, have limitations on concurrent HTTP connections. It is essential to understand these limitations because we can run into undesired situations whereby an application would function incorrectly. In this article, we will review everything that you, as a developer, need to be familiar with regarding concurrent HTTP connections.

Browser

Browsers adhere to protocols - and the HTTP 1.1 protocol states that a single client (a user client) should not maintain more than two concurrent connections. Now, some older browsers do enforce this, however, generally speaking, newer browsers - often referred to as "modern" browsers - allow a more generous limit. Here's a more precise list:

  • IE 7: 2 connections
  • IE 8 & 9: 6 connections
  • IE 10: 8 connections
  • IE 11: 13 connections
  • Firefox, Chrome (Mobile and Desktop), Safari (Mobile and Desktop), Opera: 6 connections

For the rest of this article remember the number 6 - this will play a crucial part when we go through our example.

Node.js

If you have worked with, learned or just read about Node.js before, you know that it is a single-threaded, non-blocking framework. This means that it allows a significant number of concurrent connections - all of this is made available by the JavaScript event loop.

The actual limit of connections in Node.js is determined by the available resources on the machine running the code and by the operating system settings as well.

Back in the early days of Node.js (think v0.10 and earlier), there was an imposed limit of 5 simultaneous connections to/from a single host. What does this mean? Under the hood when you are using the Node.js built-in HTTP module or any other module that uses the HTTP module like Express.js or Restify, you are in fact using a connection pool and HTTP keep-alive. This is great for performance improvement - think about the cycle like the following: an HTTP request is processed, this opens a TCP connection, for a new request an existing TCP connection can be used. (Without the keep-alive the process would be less performant by having to create a TCP connection, serve a response close the TCP connection and start this again for the next request)

In version higher than 0.10 the maxSockets value has been changed to Infinity.

The keep-alive is sent by the browser and we can easily see this if we log the request object in Node.js in the appropriate location. It should yield something similar to this (example taken from a Restify server):

headers:
{ host: 'localhost:3000',
'content-type': 'text/plain;charset=UTF-8',
origin: 'http://127.0.0.1:8080',
'accept-encoding': 'gzip, deflate',
connection: 'keep-alive',

Example

Let's take a look at a very straightforward example. Let's assume that we have some sort of a frontend where we are sending data to a backend (this is usually how modern applications work, a frontend framework making requests to a Backend API). For the our example, the data that we are sending is less important - it's equally applicable to a bulk file upload or anything else.

Trivia: I have in fact came across this issue while working on an application that did a bulk upload of images and sent it to a backend API for further processing.

Let's create a simple Restify API server:

const restify = require('restify');
const corsMiddleware = require('restify-cors-middleware');
const port = 3000;
const server = restify.createServer();
const bunyan = require('bunyan'); const cors = corsMiddleware({
origins: ['*'],
}); server.use(restify.plugins.bodyParser());
server.pre(cors.preflight);
server.use(cors.actual); server.post('/api', (req, res) => {
const payload = req.body;
console.log(`Processing: ${payload}`);
}); server.listen(port, () => console.info(`Server is up on ${port}.`));

This is very straightforward. Astute readers would already have noticed a somewhat crucial mistake in the code above but don't worry; it is made deliberately. So this API receives data sent via an HTTP POST request and displays a log message stating that it is processing whatever was sent as part of the request. (Again, the processing could be whatever we wanted, but for this discussion, it's just a simple console statement.)

Let's also create a simple frontend. Let's create a very simple index.htmland add the following content in between <script> tags:

const array = Array.from(Array(10).keys());
array.forEach(arrayItem => {
fetch('http://localhost:3000/api', {
method: 'POST',
mode: 'cors',
body: JSON.stringify(`hello${arrayItem}`)
})
.then(response => console.log(response.json()))
.catch(error => console.error(`Fetch Error: `, error));
});

Here, the Fetch API is used to iterate through 9 items (mimicking an upload of 9 files for example) and sending 9 HTTP POST requests to the Restify API discussed earlier.

Start up the API, also load the index.html via an HTTP server and let's see the results.

Here are two easy ways of firing up an HTTP server in an easy way: either use python -m SimpleHTTPServer 8000 (v2) or python -m http.server 8080 (v3). Or do a global npm install of http-server and then just do http-server from the folder where you have the index.html file.

It's fascinating what we see. Even though we have made 9 HTTP POSTrequests only six have arrived to the Restify API since we see 6 log statements.

However, if you wait about 2 minutes, additional log statements will appear.

So what is going on here?

Remember what we said before - the browser (in this case Safari) is capable of making six requests to the same host (in this case the connection is between our browser and the API running on port 3000 on localhost).

The connection is kept alive because we are not returning anything from the Node.js API. This was the mistake that I have deliberately made to make a point. So the browser sends six requests, and Node.js receives these but it never sends any information back rendering the remaining requests to be blocked.

So why are the other log statements visible later? The answer is simple: there's also a timeout, which is by default 2 minutes. After 2 minutes the request is cleared, so new requests are processed.

Let's update our code with these values:

server.server.maxConnections = 20;
function getConnections() {
server.server.getConnections((error, count) => console.log(count));
}
// add getConnections() in the API call:
server.post('/api', (req, res) => {
// ...
getConnections();
});

The server.server.maxConnections = 20; is there just to make a point that no matter how big this number is it's not going to change the outcome because we are still not returning anything (remember it is set to be Inifity anyway):

However, add the following setting to change the behaviour:

server.server.setTimeout(500);

The result is going to be a lot different. Since we are overwriting the timeout of the server, we only wait 500 ms and get rid of a pending request, allowing new requests to come in.

Please note that this is not a real solution to this problem, it is just for demonstration purposes.

Solving the problem

The right way to solve this problem is of course to return a response from the API:

server.post('/api', (req, res) => {
const payload = req.body;
console.log(`Processing: ${payload}`);
return res.json(`Done processing: ${payload}`);
});

Now all data is going to be processed just fine:

All uploads are now processed just fine.

Remember, res.json() under the hood uses res.send() which in turn also uses res.end() to send a response and to end it. This is true for both Restify and Express.js as well.

Conclusion

What is the moral of the story? Always close HTTP connections - no matter how, but close them - if you're making API calls consult the API documentation as well to close any active HTTP connection.

Concurrent HTTP connections in Node.js的更多相关文章

  1. 深入浅出node.js游戏服务器开发1——基础架构与框架介绍

    2013年04月19日 14:09:37 MJiao 阅读数:4614   深入浅出node.js游戏服务器开发1——基础架构与框架介绍   游戏服务器概述 没开发过游戏的人会觉得游戏服务器是很神秘的 ...

  2. What are some advantages of using Node.js over a Flask API?

    https://www.quora.com/What-are-some-advantages-of-using-Node-js-over-a-Flask-API Flask is a Python w ...

  3. 002/Node.js(Mooc)--Http知识

    1.什么是Http 菜鸟教程:http://www.runoob.com/http/http-tutorial.html 视频地址:https://www.imooc.com/video/6713 h ...

  4. (翻译)《Hands-on Node.js》—— Introduction

    今天开始会和大熊君{{bb}}一起着手翻译node的系列外文书籍,大熊负责翻译<Node.js IN ACTION>一书,而我暂时负责翻译这本<Hands-on Node.js> ...

  5. JavaScript(Node.js)+ Selenium自动化测试

    Selenium is a browser automation library. Most often used for testing web-applications, Selenium may ...

  6. Node.js连接Mysql,并把连接集成进Express中间件中

    引言 在node.js连接mysql的过程,我们通常有两种连接方法,普通连接和连接池. 这两种方法较为常见,当我们使用express框架时还会选择使用中间express-myconnection,可以 ...

  7. node.js + webstorm :配置开发环境

    一.配置开发环境: 1.先安装node (1).访问http://nodejs.org打开安装包,正常安装,点击next即可. 为了测试是否安装成功,打开命令提示符,输入node,则进入node.js ...

  8. Node.js Web 开发框架大全《中间件篇》

    这篇文章与大家分享优秀的 Node.js 中间件模块.Node 是一个服务器端 JavaScript 解释器,它将改变服务器应该如何工作的概念.它的目标是帮助程序员构建高度可伸缩的应用程序,编写能够处 ...

  9. [译]简单得不得了的教程-一步一步用 NODE.JS, EXPRESS, JADE, MONGODB 搭建一个网站

    原文: http://cwbuecheler.com/web/tutorials/2013/node-express-mongo/ 原文的源代码在此 太多的教程教你些一个Hello, World!了, ...

随机推荐

  1. OSX下面用ffmpeg抓取桌面以及摄像头推流进行直播

    参考博客 http://blog.chinaunix.net/uid-11344913-id-4665455.html 在osx系统下通过ffmpeg查看设备 ffmpeg -f avfoundati ...

  2. MikroTik RouterOS安装到SATA硬盘

    其实这个问题再5.x以上的版本就已经不存在这个问题,基本现在的版本都支持SATA,如果不支持,估计用的是2.x版本的,那么只需要设置成混合模式(百度)即可.

  3. ARM汇编编程基础之一 —— 寄存器

    ARM的汇编编程,本质上就是针对CPU寄存器的编程,所以我们首先要弄清楚ARM有哪些寄存器?这些寄存器都是如何使用的? ARM寄存器分为2类,普通寄存器和状态寄存器 寄存器类别 寄存器在汇编中的名称 ...

  4. Beego开源项目 收藏

    官方收藏的项目 集成开发平台:基于 Golang 的快速开发平台,平台已经集成权限管理,菜单资源管理,域管理,角色管理,用户管理,组织架构管理,操作日志管理等等 OPMS - 是一款项目管理 + OA ...

  5. .NET轻量级ORM组件Dapper修炼手册

    一.摘要 1.1.为什么叫本次的分享课叫<修炼手册>? 阿笨希望本次的分享课中涉及覆盖的一些小技巧.小技能给您带来一些帮助.希望您在日后工作中把它作为一本实际技能手册进行储备,以备不时之需 ...

  6. dbMigration .NET 数据同步迁移工具

    官网:http://fishcodelib.com/DBMigration.htm

  7. JAVA各种系统架构图及其简介

    1.spring架构图 Spring是一个开源框架,是为了解决企业应用程序开发复杂性而创建的.框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为J2EE应用程序开发提供集成的框 ...

  8. ES6的一些基本用法

    ● let ● variable hoisting ● arrow Function, Lambda表达式 ● Destructuring Assignments 解构赋值 ● 默认参数值 Defau ...

  9. 发布网站时应该把debug设置false

    在ASP.NET项目根目录下的Web.config中有这样的一个节点: <compilation debug="true" targetFramework="4.5 ...

  10. C#高级编程小结

    小结 这几章主要介绍了如何使用新的dynamic类型,还讨论了编译器在遇到dynamic类型时会做什么.还讨论了DLP,可以把它包含在简单的应用程序中.并通过Pythin使用DLR,执行Python脚 ...