当前位置: 首页 > 科技观察

说说支持HTTP模块的No.js

时间:2023-03-23 10:56:28 科技观察

1HTTP解析器No.js使用Node.js的HTTP解析器llhttp实现HTTP协议解析。llhttp负责解析HTTP消息。No.js要做的就是保存解析结果,封装具体的能力。看看No.js是如何包装llhttp的。classHTTP_Parser{public:HTTP_Parser(llhttp_typetype,parser_callbackcallbacks={}){llhttp_init(&parser,type,&HTTP_Parser::settings);//setdataafterllhttp_init,becausellhttp_initwillcallmemsettofillzerotomemoryparser.data=this;memset((void*)&callback,0,sizeof(callback));callback=callbacks;}inton_message_begin(llhttp_t*parser);inton_status(llhttp_t*parser,constchar*at,size_tlength);inton_url(llhttp_t*parser,constchar*at,size_tlength);inton_header_field(llhttp_t*parser,constchar*at,size_tlength);inton_header_value(llhttp_t*parser,constchar*at,size_tlength);inton_headers_complete(llhttp_t*parser);inton_body(llhttp_t*parser,constchar*at,size_tlength);inton_message_complete(llhttp_t*parser);intparse(constchar*data,intlen);voidprint();private:unsignedcharmajor_version;unsignedcharminor_version;unsignedcharupgrade;unsignedcharkeepalive;time_tparse_start_time;time_theader_end_time;time_tmessage_end_time;stringurl;stringstatus;vector键;向量<字符串>值;弦体;llhttp_tparser;parser_callback回调;静态llhttp_settings_tsettings;};将llhttp的解析结果存储在HTTP_Parser对象中。把HTTP_Parser类的成员函数转成c函数作为llhttp的回调是很麻烦的。问题是llhttp执行回调时如何找到对应的HTTP_Parser对象。例如llhttp的on_message_begin回调格式为typedefint(*llhttp_cb)(llhttp_t*);我们看到回调中只有llhttp相关的数据结构,并不能获取到HTTP_Parser对象。最后,我们发现llhttp提供了数据字段关联上下文。所以llhttp和HTTP_Parser的context是在初始化HTTP_Parser的时候关联的。HTTP_Parser(llhttp_typetype,parser_callbackcallbacks={}){llhttp_init(&parser,type,&HTTP_Parser::settings);parser.data=this;}我们可以在llhttp回调中通过data字段获取到HTTP_Parser对象。下面是所有钩子的实现。llhttp_settings_tNo::HTTP::HTTP_Parser::settings={[](llhttp_t*parser){return((HTTP_Parser*)parser->data)->on_message_begin(parser);},[](llhttp_t*parser,constchar*data,size_tlen){return((HTTP_Parser*)parser->data)->on_url(parser,data,len);},[](llhttp_t*parser,constchar*data,size_tlen){return((HTTP_Parser*)parser->data)->on_status(parser,data,len);},[](llhttp_t*parser,constchar*data,size_tlen){return((HTTP_Parser*)parser->data)->on_header_field(parser,data,len);},[](llhttp_t*parser,constchar*data,size_tlen){return((HTTP_Parser*)parser->data)->on_header_value(parser,data,len);},[](llhttp_t*parser){return((HTTP_Parser*)parser->data)->on_headers_complete(parser);},[](llhttp_t*parser,constchar*data,size_tlen){return((HTTP_Parser*)parser->data)->on_body(解析器,data,len);},[](llhttp_t*parser){return((HTTP_Parser*)parser->data)->on_message_complete(parser);}};这样就完成了llhttp和No.js的关联。解析完HTTP协议,最后还是要回调No.js的JS层。HTTP_Parser目前支持三种回调。structparser_callback{void*data;p_on_headers_completeon_headers_complete;p_on_bodyon_body;p_on_body_completeon_body_complete;};2HTTPC++模块完成对llhttp的封装后,需要将此能力暴露给JS层。看看C++模块到定义。classParser:publicBaseObject{public:Parser(Environment*env,Localobject):BaseObject(env,object){//注册到HTTP_Parser的回调parser_callbackcallback={this,...,...,[](on_body_complete_infoinfo,parser_callbackcallback){Parser*parser=(Parser*)callback.data;Localcb;Localcontext=parser->env()->GetContext();Isolate*isolate=parser->env()->GetIsolate();Localkey=newStringToLcal(isolate,"onBodyComplete");parser->object()->Get(context,key).ToLocal(&cb);//回调JS层if(!cb.IsEmpty()&&cb->IsFunction()){Localargv[]={newStringToLcal(isolate,info.body.c_str())};cb.As()->Call(context,parser->object(),1,argv);}},};httpparser=newHTTP_Parser(HTTP_REQUEST,callback);}voidParse(constchar*data,size_tlen);staticvoidParse(constFunctionCallbackInfo&args);staticvoidNew(constFunctionCallbackInfo)<值>&args);私有:HTTP_Parser*h解析器;};C++模块的定义很简单,就是对HTTP_Parser的封装,然后通过V8voidNo::HTTP::Init(Isolate*isolate,Localtarget){Local将能力输出到JS层parser=FunctionTemplate::New(isolate,No::HTTP::Parser::New);解析器->InstanceTemplate()->SetInternalFieldCount(1);parser->SetClassName(newStringToLcal(isolate,"HTTPParser"));parser->PrototypeTemplate()->Set(newStringToLcal(isolate,"parse"),FunctionTemplate::New(isolate,No::HTTP::Parser::Parse));setObjectValue(isolate,target,"HTTPParser",parser->GetFunction(isolate->GetCurrentContext()).ToLocalChecked());}我们看到C++模块将HTTPParser导出到JS层,并提供了parse方法。JS层从TCP层拿到数据后,执行parse对HTTP协议进行解析。下面看一下parse对应函数No::HTTP::Parser::Parse的实现。voidNo::HTTP::Parser::Parse(constFunctionCallbackInfo&args){Parser*parser=(Parser*)unwrap(args.This());LocalarrayBuffer=args[0].As();std::shared_ptrbacking=arrayBuffer->GetBackingStore();constchar*data=(constchar*)backing->Data();parser->Parse(data,strlen(data));}先解析通过args获取C++对象Parser(熟悉Node.js的同学应该很容易理解这种处理方式)。然后调用HTTP_Parser的parse方法。在解析过程中,llhttp会执行HTTP_Parser的回调,HTTP_Parser会执行Parser对象的回调,Parser会执行JS的回调。比如解析body后执行js层回调。[](on_body_complete_infoinfo,parser_callbackcallback){Parser*parser=(Parser*)callback.data;Localcb;Localcontext=parser->env()->GetContext();Isolate*isolate=parser->env()->GetIsolate();Localkey=newStringToLcal(isolate,"onBodyComplete");parser->object()->Get(context,key).ToLocal(&cb);if(!cb.IsEmpty()&&cb->IsFunction()){Localargv[]={newStringToLcal(isolate,info.body.c_str())};cb.As()->Call(context,parser->object(),1,argv);}},就是找到JS设置的onBodyComplete函数并执行。结构如下。3、JS层完成底层封装和能力导出,接下来是JS层的实现。首先,让我们看一个使用示例。const{console,}=No;const{http}=No.libs;http.createServer({host:'127.0.0.1',port:8888},(req,res)=>{console.log(JSON.stringify(req.headers));req.on('data',(buffer)=>{console.log(buffer);});});和Node.js很像,下面看具体实现。首先看TCP层的封装。classServerextendsevents{fd=-1;connections=0;constructor(options={}){super();constfd=tcp.socket(constant.domain.AF_INET,constant.type.SOCK_STREAM);this.fd=fd;tcp.bind(fd,options.host,options.port);tcp.listen(fd,512,(clientFd)=>{this.connections++;constserverSocket=newServerSocket({fd:clientFd});this.emit('connection',serverSocket);});}}在创建Server的时候,会监听传入的地址来启动一个server。监听回调的执行表明有连接。我们创建一个新的ServerSocket对象来表示与客户端通信的Socket。并触发上层的连接事件。然后看ServerSocket的实现classServerSockettextendsSocket{constructor(options={}){super(options);this.fd=options.fd;this.read();}read(){constbuffer=newArrayBuffer(1024);tcp.read(this.fd,buffer,0,(status)=>{this.emit('data',buffer);this.read();})}}ServerSocket的实现目前很简单,主要是读取数据并触发数据事件,因为TCP只负责数据传输,不负责数据分析。有了这个能力,我们再看看http层的实现。functioncreateServer(...arg){returnnewServer(...arg);}classServerextendsNo.libs.tcp.Server{constructor(options={},cb){super(options);this.options=options;if(typeofcb==='function'){this.on('request',cb);}this.on('connection',(socket)=>{newHTTPRequest({socket,server:this});});}}http该模块继承自tcp模块,所以当我们调用http.createServer时,会先执行tcp模块启动一个服务器,http层监听连接事件,等待连接的到来。当连接到达时,http创建一个HTTPRequest对象来表示http请求。classHTTPRequesttextendsNo.libs.events{socket=null;httpparser=null;constructor({socket,server}){super();this.server=server;this.socket=socket;this.httpparser=newHTTPParser();this.httpparser.onHeaderComplete=(data)=>{this.major=data.major;this.minor=data.minor;this.keepalive=data.keepalive;this.upgrade=data.upgrade;this.headers=data.headers;this.server.emit('request',this);}this.httpparser.onBody=(data)=>{this.emit('data',data);}this.httpparser.onBodyComplete=(data)=>{//console.log(data);}socket.on('data',(buffer)=>{this.httpparser.parse(buffer);});}}HTTPRequest的逻辑如下1.保存底层socket2.新建一个HTTPParser,解析HTTP协议。3、监听数据事件,收到TCP层数据后调用HTTP解析器进行解析。4.注册HTTP解析的回调钩子,如前所述。No.js解析完HTTP头,即执行onHeaderComplete回调后,会触发request事件回调业务层,也就是createServer传入的回调。业务层可以监听HTTPRequest的数据事件。当HTTP请求有body数据时,会注册HTTPRequest的data事件回调业务层。4小结虽然HTTP模块目前只是粗略的实现,但是实现过程中涉及到的内容还是很多的,以后有时间再完善。有兴趣的同学可以去https://github.com/theanarkh/No.js一探究竟。

最新推荐
猜你喜欢