Concurrent HTTP connections in Node.js
原文: 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 therequest
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.html
and 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) orpython -m http.server 8080
(v3). Or do a globalnpm
install ofhttp-server
and then just dohttp-server
from the folder where you have theindex.html
file.
It's fascinating what we see. Even though we have made 9 HTTP POST
requests 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的更多相关文章
- 深入浅出node.js游戏服务器开发1——基础架构与框架介绍
2013年04月19日 14:09:37 MJiao 阅读数:4614 深入浅出node.js游戏服务器开发1——基础架构与框架介绍 游戏服务器概述 没开发过游戏的人会觉得游戏服务器是很神秘的 ...
- 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 ...
- 002/Node.js(Mooc)--Http知识
1.什么是Http 菜鸟教程:http://www.runoob.com/http/http-tutorial.html 视频地址:https://www.imooc.com/video/6713 h ...
- (翻译)《Hands-on Node.js》—— Introduction
今天开始会和大熊君{{bb}}一起着手翻译node的系列外文书籍,大熊负责翻译<Node.js IN ACTION>一书,而我暂时负责翻译这本<Hands-on Node.js> ...
- JavaScript(Node.js)+ Selenium自动化测试
Selenium is a browser automation library. Most often used for testing web-applications, Selenium may ...
- Node.js连接Mysql,并把连接集成进Express中间件中
引言 在node.js连接mysql的过程,我们通常有两种连接方法,普通连接和连接池. 这两种方法较为常见,当我们使用express框架时还会选择使用中间express-myconnection,可以 ...
- node.js + webstorm :配置开发环境
一.配置开发环境: 1.先安装node (1).访问http://nodejs.org打开安装包,正常安装,点击next即可. 为了测试是否安装成功,打开命令提示符,输入node,则进入node.js ...
- Node.js Web 开发框架大全《中间件篇》
这篇文章与大家分享优秀的 Node.js 中间件模块.Node 是一个服务器端 JavaScript 解释器,它将改变服务器应该如何工作的概念.它的目标是帮助程序员构建高度可伸缩的应用程序,编写能够处 ...
- [译]简单得不得了的教程-一步一步用 NODE.JS, EXPRESS, JADE, MONGODB 搭建一个网站
原文: http://cwbuecheler.com/web/tutorials/2013/node-express-mongo/ 原文的源代码在此 太多的教程教你些一个Hello, World!了, ...
随机推荐
- centos 7下安装MySQL5.7 的安装和配置
原文链接: http://blog.csdn.net/xyang81/article/details/51759200 安装环境:CentOS7 64位 MINI版,安装MySQL5.7 1.配置Y ...
- 利用最新的CentOS7.5,hadoop3.1,spark2.3.2搭建spark集群
1. 桥接模式,静态ip上外网:vi /etc/sysconfig/network-scripts/ifcfg-ens33 TYPE=EthernetPROXY_METHOD=noneBROWSER_ ...
- ant design的一些坑
1.在本地修改ant design的某些样式可以生效,但在线上就失效了.比如collapse组件里的箭头图标在本地和在线上的类名有变化,本地类名,线上类名:箭头图标的svg样式在线上会自动添加一个内联 ...
- spring cloud 学习(8) - sleuth & zipkin 调用链跟踪
业务复杂的微服务架构中,往往服务之间的调用关系比较难梳理,一次http请求中,可能涉及到多个服务的调用(eg: service A -> service B -> service C... ...
- LPC-LINK 2
LPC-Link 2 is an extensible, stand-alone debug adapter that can be configured to support various dev ...
- W3wp.exe占用CPU及内存资源
问题背景 最近使用一款系统,但是经常出现卡顿或者用户账号登录不了系统.后来将问题定位在了服务器中的“w3wp.exe”这个进程.在我们的用户对系统进行查询.修改等操作后,该进程占用大量的CPU以及内存 ...
- [SQL ERROR 800]Corresponding types must be compatible in CASE expression.
SQL应用报错800.Corresponding types must be compatible in CASE expression. 错误描述: 11:00:51 [SELECT - 0 ro ...
- iOS 实现复选框 checkbox
-(void)checkboxClick:(UIButton *)btn{ btn.selected = !btn.selected;} - (void)viewDidLoad {UIButto ...
- C#编程(三十二)----------数组基础
数组 如果需要使用同一类型的多个对象,就可以使用数组.数组是一种数据结构,他可以包含同一类型的多个元素. 数组的声明 在声明数组时,应先定义数组中元素的类型,其后是一对方括号核一遍变量名.例如:生命一 ...
- Cocos2d-x之CCMenu
from://http://blog.linguofeng.com/archive/2012/11/14/cocos2d-x-CCMenu.html Cocos2d-x之CCMenu Cocos2dx ...