nodejs中用zlib解压gzip

为了减少网络传输数据量,http传输过程中会采用通用的压缩算法来压缩数据,gzip属于最常用的压缩算法。

使用node的http模块发送请求时并没有帮我们进行解压,因此我们需要手动去判断gzip。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var http = require('http');
var options = {
	hostname: 'www.qq.com',
	port: 80,
	method: 'get',
	headers: {
		'Accept-Encoding': 'gzip'
	}
}

http.request(options, handler);

function handler(responder) {
	// do something
}

我们设置http头Accept-Encoding为gzip告诉服务器我们支持gzip压缩,服务器收到请求后,返回responder头中content-encoding带有gzip表明返回的数据为gzip压缩过的内容,node中可以通过responder.headers['content-encoding']来判断服务器返回内容是否gzip压缩过。

1
2
3
4
5
function handler(responder) {
	if(responder.headers['content-encoding'].indexOf('gzip') != -1) {
		// 解压gzip
	}
}

已经可以判断服务器返回的是gzip压缩过的内容,接下来我们需要去解压,幸好node提供了zlib模块。

我们用zlib模块来解压gzip

1
2
3
4
var zlib = require('zlib');
var fs = require('fs');
var gunzipStream = zlib.createGunzip();
var toWrite = fs.createWriteStream('qq.html');

zlib.createGunzip是一个transform流,通过pipe我们可以很方便解压gzip

1
2
3
4
5
function handler(responder) {
	if(responder.headers['content-encoding'].indexOf('gzip') != -1) {
		responder.pipe(gunzipStream).pipe(toWrite);
	}
}

若是要让服务端支持gzip压缩可以先判断请求头的accept-encoding是否含有gzip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var fs = require('fs');
var http = require('http');
var zlib = require('zlib');

http.createServer(function(req, res) {
	var file = fs.createReadStream('./qq.html');
	var acceptEncoding = req.headers['accept-encoding'];
	if(acceptEncoding && acceptEncoding.indexOf('gzip') != -1) {
		var gzipStream = zlib.createGzip();
		// 设置返回头content-encoding为gzip
		res.writeHead(200, {
			"content-encoding": "gzip"
		});
		file.pipe(gzipStream).pipe(res);
	} else {
		res.writeHead(200);
		// 不压缩
		file.pipe(res);
	}
}).listen(8080);

使用curl测试一下

1
2
3
4
# 不带有Accept-Encoding:gzip 返回正常文本
curl localhost:8080
# 带Accept-Encoding:gzip 返回压缩过的文件
curl -H "Accept-Encoding:gzip" localhost:8080

通过zlib可以实现客户端和服务端对gzip的压缩和解压

在express中提供了compress的中间件用来处理gzip

谈谈nodejs中http.Agent

http协议使用“你问-我答”的方式来传输数据,即客户端通过发送请求到服务端,服务端响应请求来完成交互。对每次交互都要经过”建立-传输-销毁“的过程,为了减少建立/销毁连接的开销,http提供了keep-alive模式,即“持久连接”对已经创建的连接可以重复使用,在浏览器中维护这些tcp连接是浏览器的行为我们不需要关心,但在服务端这些tcp连接需要我们自己去维护,还好node提供了http.Agent来帮我们维护.

node中通过http.Agent来管理这些可复用的连接,我们可以创建一个http.Agent实例

1
var agent = new http.Agent();

http.globalAgent是node默认创建的一个Agent实例,对http请求没有指定agent情况下,默认使用这个实例.

我们看看agent代理内部是如何管理tcp链接的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Agent.prototype.addRequest = function(req, host, port, localAddress) {
  var name = host + ':' + port;
  if (localAddress) {
    name += ':' + localAddress;
  }
  if (!this.sockets[name]) {
    this.sockets[name] = [];
  }
  if (this.sockets[name].length < this.maxSockets) {
    // If we are under maxSockets create a new one.
    req.onSocket(this.createSocket(name, host, port, localAddress, req));
  } else {
    // We are over limit so we'll add it to the queue.
    if (!this.requests[name]) {
      this.requests[name] = [];
    }
    this.requests[name].push(req);
  }
};

agent通过将host、port和localAddress作为key用一个数组来存储可以复用的socket,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  self.on('free', function(socket, host, port, localAddress) {
    var name = host + ':' + port;
    if (localAddress) {
      name += ':' + localAddress;
    }

    if (!socket.destroyed &&
        self.requests[name] && self.requests[name].length) {
      self.requests[name].shift().onSocket(socket);
      if (self.requests[name].length === 0) {
        // don't leak
        delete self.requests[name];
      }
    } else {
      // If there are no pending requests just destroy the
      // socket and it will get removed from the pool. This
      // gets us out of timeout issues and allows us to
      // default to Connection:keep-alive.
      socket.destroy();
    }
  });

当socket完成一次请求后触发free事件,从request队列中进行下一次的请求,因此可以实现。

maxSockets:agent限制了对某个地址的最大请求数目,可以理解为浏览器限制对同一个域名端口的最大并发数.

为什么要进行限制呢?因为在linux平台上,操作系统为每个tcp连接都要创建一个socket句柄,而每个socket句柄同时也是个文件句柄,而操作系统对单一进程限制了最大可打开文件数量,通过ulimit -n命令可以查看当前操作系统限制创建文件数量,也可以用来设置最大可打开文件数。

1
2
#设置单进程最大可打开文件数为256
ulimit -n 256

因此对一个线程可以创建的tcp连接有限,设置maxSockets做成防止对同一地址端口发出请求过大,我们可以通过修改maxSockets来设置最大连接数量.

1
http.globalAgent.maxSockets = 10; //设置最大同时请求数目为10个

http.Agent.defaultMaxSockets定义了默认的最大连接数为5。

默认情况下,http使用http.globalAgent来管理这些连接,我们可以通过传入agent参数来指定我们自己的agent。

1
2
3
4
5
6
7
8
9
10
11
12
var http = require('http');
var agent = new http.Agent();
var option = {
	hostname: 'www.qq.com',
	port: 80,
	method: 'get',
	path: '/',
	agent: agent
}
http.request(option, function(res) {
	// do something
});

也可以传agent为false来取消这个http请求的keep-alive的行为。