标签:ret protocol realm asc 管理 one app write cte
问题1:HTTP服务继承了TCP服务模型,是从connection为单位的服务到以request为单位的服务的封装,那么request事件何时触发?
注意:在开启keepalive后,一个TCP会话可以用于多次请求和响应,在请求产生的过程中,http模块拿到传递过来的数据,调用二进制模块http_parser模块进行解析,在解析完请求报文的报文头以后,触发request事件,调用用户的业务逻辑。客户端对象的reponse事件也是一样的,只要解析完了响应头就会触发,同时传入一个响应对象以供操作响应,后续报文以只读流的方式提供。同时,不许知道这时候的response对象是一个http.IncomingMessage实例!
问题2:http请求中的req对象的内部数据是怎么样的?
- IncomingMessage {
- httpVersionMajor: 1,
- httpVersionMinor: 1,
- httpVersion: ‘1.1‘,
- complete: false,
-
- headers:
- { host: ‘localhost:1337‘,
- connection: ‘keep-alive‘,
- accept: ‘text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*
- ‘,
- ‘Upgrade-Insecure-Requests‘,
- ‘1‘,
- ‘User-Agent‘,
- ‘Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome
- /49.0.2623.110 Safari/537.36‘,
- ‘Accept-Encoding‘,
- ‘gzip, deflate, sdch‘,
- ‘Accept-Language‘,
- ‘zh-CN,zh;q=0.8‘,
- ‘Cookie‘,
- ‘qinliang=s%3ABDOjujVhV0DH9Atax_gl4DgZ4-1RGvjQ.OeUddoRalzB4iSmUHcE8oMziad4I
- g7jUT1REzGcYcdg; blog=s%3A-ZkSm8urr8KsXAKsZbSTCp8EWOu7zq2o.Axjo6YmD2dLPGQK9aD1mR
- 8FcpOzyHaGG6cfGUWUVK00‘ ],
- trailers: {},
- rawTrailers: [],
- upgrade: false,
- url: ‘/‘,
- method: ‘GET‘,
- statusCode: null,
- statusMessage: null,
- httpVersionMajor: 1,
- httpVersionMinor: 1,
- httpVersion: ‘1.1‘,
- complete: false,
- headers:
- { host: ‘localhost:1337‘,
- connection: ‘keep-alive‘,
- accept: ‘text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*
- ‘,
- ‘Upgrade-Insecure-Requests‘,
- ‘1‘,
- ‘User-Agent‘,
- ‘Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome
- /49.0.2623.110 Safari/537.36‘,
- ‘Accept-Encoding‘,
- ‘gzip, deflate, sdch‘,
- ‘Accept-Language‘,
- ‘zh-CN,zh;q=0.8‘,
- ‘Cookie‘,
- ‘qinliang=s%3ABDOjujVhV0DH9Atax_gl4DgZ4-1RGvjQ.OeUddoRalzB4iSmUHcE8oMziad4I
- g7jUT1REzGcYcdg; blog=s%3A-ZkSm8urr8KsXAKsZbSTCp8EWOu7zq2o.Axjo6YmD2dLPGQK9aD1mR
- 8FcpOzyHaGG6cfGUWUVK00‘ ]
注意:对于报文体部分被抽象为一个只读流,如果业务逻辑需要读取报文体中的数据必须要在数据流结束后才能进行操作。如数据读取如下:
- function(req,res){
- var buffers=[];
- req.on(‘data‘,function(trunk){
- buffers.push(trunk);
- }).on(‘end‘,function(){
- var buffer=Buffer.concat(buffers);
-
- res.end(buffer);
-
- })
- }
下面两种情况会返回IncomingMessage:
第一种情况是服务器端的request对象
- var http=require(‘http‘);
- http.createServer(function(req,res){
- res.end("IncomingMessage="+(req instanceof http.IncomingMessage));
-
- }).listen(1337);
注意:服务器的request事件回调函数第一个参数为http.IncomingMessage对象
第二种情况就是客户端的res对象
- var http=require(‘http‘);
- var options={
- hostname:‘localhost‘,
- port:1337,
- path:‘/‘,
- method:‘GET‘
- };
- var req=http.request(options,function(res){
-
- res.on(‘data‘,function(chunk){
-
- console.log(res instanceof http.IncomingMessage);
-
- })
- })
- req.end();
问题3:http.ClientRequest 对象如何获取,含有那些方法?
http.request(options[, callback])
Node.js在每一个服务器上保持多个连接,这个方法可以让客户端发送一个请求,options如果是string那么就会用url.parse方法自动解析,对象包含的属性如下:
protocal:默认为"http:"
host:是主机名
hostname:是host的别名,hostname的优先级高于host
family:表示解析host/hostname时候的Ip类型,可以是4/6,如果没有指定那么会同时用IPV4/IPV6
port:默认为80
localAddress:发送请求的本地网卡
socketPath:Unix域套接字,要么使用host:port/socketPath
method:默认为get
path:默认为/。路径中不能使用空格
headers:客户端发送的HTTP头
auth:使用基本认证。如"user:pass"去计算认证头Authorization
服务器端的代码:
- var http=require(‘http‘);
- http.createServer(function(req,res){
- var auth=req.headers[‘authorization‘]||‘‘;
-
- var parts=auth.split(‘ ‘);
- var method=parts[0];
- var encoded=parts[1];
- var decoder=new Buffer(encoded,‘base64‘).toString(‘utf-8‘).split(":");
-
- var user=decoder[0];
-
- var pass=decoder[1];
-
- if(!user==‘qinliang‘&&!pass==‘123‘){
- res.setHeader(‘WWW-Authenticate‘,‘Basic realm="Secure Area"‘);
- res.writeHead(401);
-
- res.end();
- }else{
- res.end(‘You are forbidden to enter!‘);
- }
-
- }).listen(8888,‘localhost‘);
客户端的代码:
- var http=require(‘http‘);
- var encode=function(username,password){
- return new Buffer(username+":"+password).toString(‘base64‘);
- }
- var options={
- hostname:‘localhost‘,
- port:8888,
- path:‘/‘,
- method:‘GET‘,
-
- auth:"qinliang:123"
-
- };
- var req=http.request(options,function(res){
-
- res.on(‘data‘,function(chunk){
-
- console.log(res instanceof http.IncomingMessage);
-
- })
- })
- req.end();
agent:用于控制Agent行为。如果指定了agent那么请求就变成Connect:keep-alive了。可以指定他的值为undefined(这时候这个值就是http.globalAgent);也可以指定为Agent对象;第三个值为false,从连接池中拿到一个Agent,默认请求为Connection:close。其中回调函数callback是作为response事件的默认事件处理函数。这个方法返回的是一个http.ClientRequest类,这个类是一个可写的流,如果需要用Post请求来上传文件,这时候就需要写ClientRequest对象。下面是用这个对象进行上传文件的操作:
- var http=require(‘http‘);
- var querystring=require(‘querystring‘);
- var postData = querystring.stringify({
- ‘msg‘ : ‘Hello World!‘
- });
- var options = {
- hostname: ‘www.google.com‘,
- port: 80,
- path: ‘/upload‘,
- method: ‘POST‘,
- headers: {
- ‘Content-Type‘: ‘application/x-www-form-urlencoded‘,
- ‘Content-Length‘: postData.length
- }
- };
- var req = http.request(options, (res) => {
- console.log(`STATUS: ${res.statusCode}`);
- console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
- res.setEncoding(‘utf8‘);
- res.on(‘data‘, (chunk) => {
- console.log(`BODY: ${chunk}`);
- });
- res.on(‘end‘, () => {
- console.log(‘No more data in response.‘)
- })
- });
- req.on(‘error‘, (e) => {
- console.log(`problem with request: ${e.message}`);
- });
- req.write(postData);
- req.end();
注意:发送给一个Connection:keep-alive就会通知Node.js当前的这个连接应该保存,并用于下一次请求;如果设置了Content-length时候,那么就会取消chunked编码;Expect请求头如果被发送,那么所有的请求头都会马上发送,如果你设置了Expect: 100-continue,这时候你必须指定timeout属性,同时监听continue事件;发送Authorization头时候,上面指定的用auth计算的Authorization值就会被覆盖。
Class: http.ClientRequest:
这个对象是通过http.request方法返回的。他代表一个正在处理的请求,但是这个请求的头已经在队列中了。这时候这个头还是可以通过setHeader(name, value), getHeader(name), removeHeader(name)改变的。实际的头部发送需要等到和数据一起发送,或者连接关闭的时候也会发送。如果你需要获取服务器端的响应,这时候需要监听response事件,这个事件当收到服务器端的响应头的时候就会触发<响应头>,其回调函数只有一个参数是一个IncomingMessage对象实例,在response事件中,我们可以为response对象添加事件,一般监听data事件。
如果没有指定任何response事件处理函数,那么所有的response响应都会被丢弃。如果你指定了resposne事件那么你必须自己从response对象上消费数据,可以通过调用response.read方法或者添加一个"data"事件处理函数,或者通过调用"resume"方法。当data被消费了以后,‘end‘事件就会触发。当然,如果读取了数据,那么这时候就会消耗内存,也可能导致最后的内存溢出错误。注意:Node.js不会监测Content-Length是否和数据体的长度一致。这个request对象实现了Writtable Stream,也是一个EventEmitter对象。我们先来看看这个http.ClientRequest类的签名:
- ClientRequest {
- domain: null,
- _events:
- { response: { [Function: g] listener: [Function] },
- socket: { [Function: g] listener: [Function] }
- },
- _eventsCount: 2,
- _maxListeners: undefined,
- output: [],
- outputEncodings: [],
- outputCallbacks: [],
- outputSize: 0,
- writable: true,
- _last: true,
- chunkedEncoding: false,
- shouldKeepAlive: false,
- useChunkedEncodingByDefault: false,
- sendDate: false,
- _removedHeader: {},
- _contentLength: null,
- _hasBody: true,
- _trailer: ‘‘,
- finished: false,
- _headerSent: false,
- socket: null,
- connection: null,
- _header: null,
- _headers:
- { host: ‘localhost:8888‘,
- authorization: ‘Basic cWlubGlhbmc6MTIz‘ },
- _headerNames: { host: ‘Host‘, authorization: ‘Authorization‘ },
- _onPendingData: null,
- agent:
- Agent {
- domain: null,
- _events: { free: [Function] },
- _eventsCount: 1,
- _maxListeners: undefined,
- defaultPort: 80,
- protocol: ‘http:‘,
- options: { path: null },
- requests: {},
- sockets: { ‘localhost:8888:‘: [Object] },
- freeSockets: {},
- keepAliveMsecs: 1000,
- keepAlive: false,
- maxSockets: Infinity,
- maxFreeSockets: 256 },
- socketPath: undefined,
- method: ‘GET‘,
- path: ‘/‘ }
这个对象有下面这些方法:
- request.abort()
- request.end([data][, encoding][, callback])
- request.flushHeaders()
- request.setNoDelay([noDelay])
- request.setSocketKeepAlive([enable][, initialDelay])
- request.setTimeout(timeout[, callback])
- request.write(chunk[, encoding][, callback])
这个对象有一下事件:
- abort事件
- connect事件
- continue事件
- response事件
- socket事件
- upgrade事件
下面展示如何通过http.ClientRequest类和http.Server类实现协议升级:(具体代码见github地址)
- const http = require(‘http‘);
- var srv = http.createServer( (req, res) => {
- res.writeHead(200, {‘Content-Type‘: ‘text/plain‘});
- res.end(‘okay‘);
- });
- srv.on(‘upgrade‘, (req, socket, head) => {
- socket.write(‘HTTP/1.1 101 Web Socket Protocol Handshake\r\n‘ +
- ‘Upgrade: WebSocket\r\n‘ +
- ‘Connection: Upgrade\r\n‘ +
- ‘\r\n‘);
-
- socket.pipe(socket);
- });
-
- srv.listen(7777, ‘127.0.0.1‘, () => {
-
- var options = {
- port: 7777,
- hostname: ‘127.0.0.1‘,
- headers: {
- ‘Connection‘: ‘Upgrade‘,
- ‘Upgrade‘: ‘websocket‘
- }
- };
-
- var req = http.request(options);
- req.end();
-
- req.on(‘upgrade‘, (res, socket, upgradeHead) => {
- console.log(‘got upgraded!‘);
- socket.end();
- process.exit(0);
- });
- });
如何从http.ClientReqeust对象发送一个CONNECT请求:
- const http = require(‘http‘);
- const net = require(‘net‘);
- const url = require(‘url‘);
- var proxy = http.createServer( (req, res) => {
- res.writeHead(200, {‘Content-Type‘: ‘text/plain‘});
- res.end(‘okay‘);
- });
- proxy.on(‘connect‘, (req, cltSocket, head) => {
-
- var srvUrl = url.parse(`http:
-
-
-
-
-
- var srvSocket = net.connect(srvUrl.port, srvUrl.hostname, () => {
-
- cltSocket.write(‘HTTP/1.1 200 Connection Established\r\n‘ +
- ‘Proxy-agent: Node.js-Proxy\r\n‘ +
- ‘\r\n‘);
-
- srvSocket.write(head);
-
- srvSocket.pipe(cltSocket);
-
- cltSocket.pipe(srvSocket);
- });
- });
-
- proxy.listen(9999, ‘localhost‘, () => {
-
- var options = {
- port: 9999,
- hostname: ‘localhost‘,
- method: ‘CONNECT‘,
- path: ‘www.google.com:80‘
- };
- var req = http.request(options);
- req.end();
-
- req.on(‘connect‘, (res, socket, head) => {
- console.log(‘got connected!‘);
-
-
- socket.write(‘GET / HTTP/1.1\r\n‘ +
- ‘Host: www.google.com:80\r\n‘ +
- ‘Connection: close\r\n‘ +
- ‘\r\n‘);
- socket.on(‘data‘, (chunk) => {
- console.log(chunk.toString());
- });
- socket.on(‘end‘, () => {
- proxy.close();
- });
- });
- });
问题4:http.Server类有哪些事件和方法?
- checkContinue事件:
- clientError事件:
- close事件:
- connect事件:
- proxy.on(‘connect‘, (req, cltSocket, head) => {
-
- var srvUrl = url.parse(`http:
-
-
-
-
- var srvSocket = net.connect(srvUrl.port, srvUrl.hostname, () => {
-
- cltSocket.write(‘HTTP/1.1 200 Connection Established\r\n‘ +
- ‘Proxy-agent: Node.js-Proxy\r\n‘ +
- ‘\r\n‘);
-
- srvSocket.write(head);
-
- srvSocket.pipe(cltSocket);
-
- cltSocket.pipe(srvSocket);
- });
- });
- connection事件:
- request事件:
- upgrade事件:
-
- srv.on(‘upgrade‘, (req, socket, head) => {
- socket.write(‘HTTP/1.1 101 Web Socket Protocol Handshake\r\n‘ +
- ‘Upgrade: WebSocket\r\n‘ +
- ‘Connection: Upgrade\r\n‘ +
- ‘\r\n‘);
-
- socket.pipe(socket);
- });
-
- server.close([callback])方法:
- server.maxHeadersCount
- server.setTimeout(msecs, callback)
- server.timeout
问题5:Class: http.ServerResponse内部机制是什么?
注意:这一部分内容比较好理解,这里就不展开讨论了
问题6:Class: http.Agent类设置的初衷是什么?
如同服务器端的实现一样,http提供的ClientRequest对象也是基于TCP实现的,在keep-alive的情况下,一个底层会话连接可以用于多次请求。为了重用TCP连接,http模块包含了一个默认的客户端代理http.globalAgent对象,他对每一个服务器端创建的连接进行了管理,默认情况下,通过ClientRequest对象对同一个服务器端发送的HTTP请求最多可以创建5个连接,他的实质为一个连接池。调用HTTP客户端对一个服务器发送10次HTTP请求时候只有5个请求处于并发状态,后续的请求需要等待某个请求完成服务后才能真正请求。(这和浏览器同一个域名下有连接限制是一样的,这里http.ClientRequest就是客户端)
http.globalAgent
全局Agent实例,默认为5个连接。
我们先来学习一下http.get方法:
http.get(options[, callback])
因为大多数的请求都是GET请求没有数据体,Node.js提供了这个方法用于发送GET请求。这个方法和http.request的唯一区别在于:这个方法会自动把方法设置为GET,同时自动调用req.end方法,如下例:
- http.get(‘http://www.google.com/index.html‘, (res) => {
- console.log(`Got response: ${res.statusCode}`);
-
-
- res.resume();
- }).on(‘error‘, (e) => {
- console.log(`Got error: ${e.message}`);
- });
Class: http.Agent
这个HTTP Agent默认使得客户端的请求使用了Connection:keep-alive,但是不需要使用KeepAlive手动关闭。如果你需要使用HTTP的KeepAlive选项,那么你需要在创建一个Agent对象的时把flag设置为true。这时候Agent就会保持在连接池中那些没有使用的连接处于活动状态已被后续使用。这时候的连接就会被显示标记从而不让Node.js的进程一直运行。当然,当你确实不需要使用KeepAlive状态的agent的时候显示的调用destroy方法还是很好的选择,这时候Socket就会被关闭。注意:当socket触发了‘close‘或者‘agentRemove‘事件的时候就会从aget池中被移除,因此如果比需要让一个HTTP请求保持长时间的打开状态,同时不想让这个连接在Agent池的时候可以使用下面的方法:
- http.get(options, (res) => {
-
- }).on(‘socket‘, (socket) => {
- socket.emit(‘agentRemove‘);
- });
但是,如果你需要把所有的连接都不让Agent池来管理可以把agent:false
- http.get({
- hostname: ‘localhost‘,
- port: 80,
- path: ‘/‘,
- agent: false
- }, (res) => {
-
- })
new Agent([options])
这个options可以是如下的内容:
。keepAlive:布尔值,默认为false,表示让Agent池中的socket保持活动状态用于未来的请求。
。keepAliveMsecs,当使用了HTTP的KeepAlive的时候,多久为keep-alive的socket发送一个TCP的KeepAlive数据包。默认为1000,但是只有keepAlive设置为true时候才可以
。maxSockets:每一个主机下面最多具有的socket数量,默认为Infinity
。maxFreeSockets:处于活动状态的最多的socket数量,默认为256,在keepAlive设置为true时候可用
注意:http.globalAgent对象所有值都是默认值的
- const http = require(‘http‘);
- var keepAliveAgent = new http.Agent({ keepAlive: true });
- options.agent = keepAliveAgent;
- http.request(options, onResponseCallback);
Agent对象有如下一系列的方法:
- agent.destroy()
- agent.freeSockets
- agent.getName(options)
- agent.maxFreeSockets
- agent.maxSockets
- agent.requests
- agent.sockets
注意:这里突然想到了为什么浏览器需要限制同域名下并发的请求的数量。原因主要有几个:第一个是为了保护服务器端,因为服务器并发处理的请求的数量也是有限制的,是客户端和服务器端一种默契的配合;第二个原因是TCP连接需要一个端口号,而端口号的数量最多是65536,而很多端口号都被系统的其他内置进程占用,而创建TCP连接需要消耗系统资源,因此这种方法可以保护操作系统的 TCP\IP 协议栈资源不被迅速耗尽,因此浏览器不好发出太多的 TCP 连接,而是采取用完了之后再重复利用 TCP 连接或者干脆重新建立 TCP 连接的方法;第三个原因是:创建TCP/IP需要一定的系统资源,而且操作系统在调用进程的时候需要切换上下文,因此也会限制浏览器并发的数量,这是操作系统一种自我保护措施!
注意:半开连接指的是 TCP 连接的一种状态,当客户端向服务器端发出一个 TCP 连接请求,在客户端还没收到服务器端的回应并发回一个确认的数据包时,这个 TCP 连接就是一个半开连接。若服务器到超时以后仍无响应,那么这个 TCP 连接就等于白费了,所以操作系统会本能的保护自己,限制 TCP 半开连接的总个数,以免有限的内核态内存空间被维护 TCP 连接所需的内存所浪费。
转 node.js里面的http模块深入理解
标签:ret protocol realm asc 管理 one app write cte
原文地址:http://www.cnblogs.com/Ewarm/p/7722904.html