当前位置: 首页 > 后端技术 > Node.js

Node源码——http模块

时间:2023-04-03 14:47:44 Node.js

http模块http模块位于/lib/http.js,我们直接看模块的核心方法createServerfunctioncreateServer(opts,requestListener){returnnewServer(opts,requestListener);}createServer函数是创建一个Server类,文件位于/lib/_http_server.jsfunctionServer(options,requestListener){if(!(thisinstanceofServer))returnnewServer(options,requestListener);if(typeofoptions==='function'){requestListener=options;选项={};}elseif(options==null||typeofoptions==='object'){options={...options};}else{thrownewERR_INVALID_ARG_TYPE('options','object',options);}//options允许传递两个参数,分别是IncomingMessage和ServerResponse//IncomingMessage是客户端请求的信息,ServerResponse是服务器响应的数据//IncomingMessage和ServerResponse允许继承一些自定义操作//IncomingMessage主要是负责序列化HTTP报文,取出报文头内容并序列化//ServerResponse提供了一些接口用于快速响应客户端,设置响应报文头内容的接口this[kIncomingMessage]=options.IncomingMessage||传入消息;这个[kServerResponse]=选项.ServerResponse||服务器响应;//Server类仍然继承自net.Server类net.Server.call(this,{allowHalfOpen:true});if(requestListener){//request请求事件触发后,调用requestListener函数this.on('request',requestListener);}//与此类似的选项。懒得写我自己的文档。//http://www.squid-cache.org/Doc/config/half_closed_clients///http://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3Fthis.httpAllowHalfOpen=false;this.on('connection',connectionListener);//超时,默认禁用this.timeout=0;//当连接闲置一定时间后,断开TCP连接,重新建立连接重新建立三次握手this.keepAliveTimeout=5000;//默认不限制响应头的最大数量。this.maxHeadersCount=null;this.headersTimeout=40*1000;//40seconds}我们需要进一步往下分析,看net.Server类是如何定位到/lib/net.jsfunctionServer(options,connectionListener){if(!(thisinstanceofServer))returnnewServer(options,连接监听器);//继承自EventEmitter类,该类属于事件类EventEmitter.call(这个);if(typeofoptions==='function'){connectionListener=options;选项={};this.on('connection',connectionListener);}elseif(options==null||typeofoptions==='object'){options={...options};if(typeofconnectionListener==='function'){this.on('connection',connectionListener);}}else{thrownewERR_INVALID_ARG_TYPE('options','Object',options);}this._connections=0;Object.defineProperty(this,'connections',{get:deprecate(()=>{if(this._usingWorkers){returnnull;}returnthis._connections;},'Server.connections属性已弃用。'+'使用Server.getConnections方法代替。','DEP0020'),set:deprecate((val)=>(this._connections=val),'Server.connectionspropertyisdeprecated.','DEP0020'),configurable:true,enumerable:错误的});这个[async_id_symbol]=-1;这个._handle=空;this._usingWorkers=false;这个._workers=[];this._unref=false;this.allowHalfOpen=options.allowHalfOpen||错误的;this.pauseOnConnect=!!options.pauseOnConnect;}Object.setPrototypeOf(Server.prototype,EventEmitter.prototype);Object.setPrototypeOf(Server,EventEmitter);从代码中可以看出,net.Server类本身就是一个事件分发中心,具体实现由多个内部方法组成。先来看看我们最常用的方法之一listenServer.prototype.listen=function(...args){constnormalized=normalizeArgs(args);//序列化后,选项包含端口和主机字段(如果已填写)varoptions=normalized[0];常量cb=归一化[1];如果(this._handle){抛出新的ERR_SERVER_ALREADY_LISTEN();}if(cb!==null){//给回调函数绑定一个一次性事件,当监听事件触发时调用回调函数this.once('listening',cb);}//...变种积压;if(typeofoptions.port==='number'||typeofoptions.port==='string'){if(!isLegalPort(options.port)){thrownewERR_SOCKET_BAD_PORT(options.port);}backlog=选项.backlog||积压从参数;//启动TCP服务器监听host:portif(options.host){//我们假设主机为localhost,端口为3000,则调用lookupAndListen方法lookupAndListen(this,options.port|0,options.主机,积压,options.exclusive,标志);}else{//未定义主机,监听未指定地址//默认地址类型4将用于搜索主服务器listenInCluster(this,null,options.port|0,4,backlog,undefined,options.exclusive);}返回这个;}//...};functionlookupAndListen(self,port,address,backlog,exclusive,flags){if(dns===undefined)dns=require('dns');//这期间使用dns模块解析host,为了得到一个ip地址dns.lookup(address,functiondoListen(err,ip,addressType){if(err){self.emit('error',err);}else{addressType=ip?addressType:4;//获取ip地址后执行listenInClusterlistenInCluster(self,ip,port,addressType,积压、未定义、独占、标志);}});}functionlistenInCluster(server,address,port,addressType,backlog,fd,exclusive,flags){exclusive=!!exclusive;//cluster是Node中的cluster模块,可以创建共享server端口的子进程//listen的作用是让一个进程监听http请求if(cluster===undefined)cluster=require('簇');if(cluster.isMaster||exclusive){//会创建一个新的句柄//_listen2设置监听的句柄,还是这样命名//避免破坏包装这个方法的代码//这个方法是最后执行的方法server._listen2(地址、端口、地址类型、积压、fd、标志);返回;}constserverQuery={地址:地址,端口:端口,地址类型:地址类型,fd:fd,标志,};//获取master的服务器句柄,并监听它cluster._getServer(server,serverQuery,listenOnMasterHandle);functionlistenOnMasterHandle(err,handle){err=checkBindError(err,port,handle);我f(err){varex=exceptionWithHostPort(err,'bind',address,port);返回server.emit('error',ex);}//重用master的服务器句柄server._handle=handle;//_listen2设置监听句柄,它仍然这样命名//以避免破坏包装此方法的代码server._listen2(address,port,addressType,backlog,fd,flags);}}//server._listen2指向的是一个setupListenHandle方法,setupListenHandle最终指向的是createServerHandle方法//如果可以创建则返回句柄,如果不能创建则返回错误码createServerHandle(address,port,addressType,fd,flags){错误=0;//在listen中分配句柄,如果bind或listen失败则清理varhandle;varisTCP=false;if(typeoffd==='number'&&fd>=0){try{handle=createHandle(fd,true);}catch(e){//不是我们可以监听的fd。这将触发错误。debug('监听无效的fd=%d:',fd,e.message);返回UV_EINVAL;}err=handle.open(fd);如果(错误)返回错误;断言(!地址&&!端口);}elseif(port===-1&&addressType===-1){handle=newPipe(PipeConstants.SERVER);if(process.platform==='win32'){varinstances=parseInt(process.env.NODE_PENDING_PIPE_INSTANCES);如果(!Number.isNaN(实例)){handle.setPendingInstances(实例);}}}else{//最终启动了一个TCP服务,当TCP端口收到数据时,会推送给HTTP进程//具体实现可能是TCP内部发送一个emit用于事件通知,并在同时将字节流转换为HTTP协议可以接受的报文格式(协议规范)handle=newTCP(TCPConstants.SERVER);isTCP=真;}if(address||port||isTCP){debug('bindto',address||'any');if(!address){//首先尝试绑定到ipv6err=handle.bind6(DEFAULT_IPV6_ADDR,port,flags);如果(错误){handle.close();//回退到ipv4returncreateServerHandle(DEFAULT_IPV4_ADDR,port);}}elseif(addressType===6){err=handle.bind6(地址,端口,标志);}else{//这一步绑定主机和端口err=handle.bind(address,port);}}returnhandle;}从上面我们可以看出,http.createServer的调用过程如下:http.createServer实际上返回了一个Server类,这个类本质上是继承自EventEmitter,用于接收和分发事件通知外部呼叫。这时候进程http.createServer().listen()步骤在底层,调用Node内置模块TCP启动一个TCP进程,被动打开等待连接。TCP端口接收连接时,接收发送方的数据,然后将接收到的字节流传递给应用层,再通过http_parser模块进行分解,得到符合http协议规范的报文格式,在事件(请求)通知的形式通知服务器类。当Server类收到事件通知时,使用IncomingMessage类解析接收到的数据,序列化HTTP消息,取出header消息内容序列化为Javascript对象格式,使用ServerResponse类提供一些快速响应client接口,设置响应消息头内容的接口,然后将这两项作为listen的回调参数中的两个参数(req,res)=>{}传入,触发回调函数。

最新推荐
猜你喜欢