您现在的位置是:亿华云 > IT科技类资讯
聊聊No.js 支持 HTTP 模块
亿华云2025-10-09 03:50:28【IT科技类资讯】8人已围观
简介1 HTTP 解析器No.js 使用 Node.js 的 HTTP 解析器 llhttp 实现 HTTP 协议的解析,llhttp 负责解析 HTTP 报文,No.js 需要做的事情是保存解析的结果并
1 HTTP 解析器
No.js 使用 Node.js 的聊聊 HTTP 解析器 llhttp 实现 HTTP 协议的解析,llhttp 负责解析 HTTP 报文,支持No.js 需要做的模块事情是保存解析的结果并封装具体的能力。看看 No.js 是聊聊如何封装 llhttp 的。
class HTTP_Parser { public: HTTP_Parser(llhttp_type type,支持 parser_callback callbacks = { }) { llhttp_init(&parser, type, &HTTP_Parser::settings); // set data after llhttp_init, because llhttp_init will call memset to fill zero to memory parser.data = this; memset((void *)&callback, 0, sizeof(callback)); callback = callbacks; } int on_message_begin(llhttp_t* parser); int on_status(llhttp_t* parser, const char* at, size_t length); int on_url(llhttp_t* parser, const char* at, size_t length); int on_header_field(llhttp_t* parser, const char* at, size_t length); int on_header_value(llhttp_t* parser, const char* at, size_t length); int on_headers_complete(llhttp_t* parser); int on_body(llhttp_t* parser, const char* at, size_t length); int on_message_complete(llhttp_t* parser); int parse(const char* data, int len); void print(); private: unsigned char major_version; unsigned char minor_version; unsigned char upgrade; unsigned char keepalive; time_t parse_start_time; time_t header_end_time; time_t message_end_time; string url; string status; vector<string> keys; vector<string> values; string body; llhttp_t parser; parser_callback callback; static llhttp_settings_t settings; };HTTP_Parser 是对 llhttp 的封装,主要是模块注册 llhttp 的钩子,llhttp 在解析 HTTP 报文的聊聊时候会回调 HTTP_Parser 的钩子。比较麻烦的支持是需要在 HTTP_Parser 对象里保存 llhttp 的解析结果,把 HTTP_Parser 类的模块成员函数转成 c 函数作为 llhttp 的回调非常麻烦,问题在于如何在 llhttp 执行回调的聊聊时候找到对应的 HTTP_Parser 对象。比如 llhttp 的支持 on_message_begin 回调格式是
typedef int (*llhttp_cb)(llhttp_t*);我们看到回调里只有 llhttp 相关的服务器托管数据结构,拿不到 HTTP_Parser 对象,模块最终发现 llhttp 提供了 data 字段关联上下文。聊聊所以在 HTTP_Parser 初始化时关联 llhttp 和 HTTP_Parser 的支持上下文。
HTTP_Parser(llhttp_type type,模块 parser_callback callbacks = { }) { llhttp_init(&parser, type, &HTTP_Parser::settings); parser.data = this; }我们在 llhttp 回调时通过 data 字段就可以取得 HTTP_Parser 对象。下面是所有钩子的实现。
llhttp_settings_t No::HTTP::HTTP_Parser::settings = { [](llhttp_t * parser) { return ((HTTP_Parser *)parser->data)->on_message_begin(parser); }, [](llhttp_t * parser, const char * data, size_t len) { return ((HTTP_Parser *)parser->data)->on_url(parser, data, len); }, [](llhttp_t * parser, const char * data, size_t len) { return ((HTTP_Parser *)parser->data)->on_status(parser, data, len); }, [](llhttp_t * parser, const char * data, size_t len) { return ((HTTP_Parser *)parser->data)->on_header_field(parser, data, len); }, [](llhttp_t * parser, const char * data, size_t len) { 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, const char * data, size_t len) { return ((HTTP_Parser *)parser->data)->on_body(parser, data, len); }, [](llhttp_t * parser) { return ((HTTP_Parser *)parser->data)->on_message_complete(parser); } };这样就完成了 llhttp 和 No.js 的关联。解析完 HTTP 协议后,最终还需要回调 No.js 的 JS 层。HTTP_Parser 目前支持三种回调。
struct parser_callback { void * data; p_on_headers_complete on_headers_complete; p_on_body on_body; p_on_body_complete on_body_complete; };2 HTTP C++ 模块
完成了 llhttp 的封装后,接着需要把这个能力暴露到 JS 层。看一下 C++ 模块到定义。
class Parser : public BaseObject { public: Parser(Environment* env, Local<Object> object): BaseObject(env, object) { // 注册到 HTTP_Parser 的回调 parser_callback callback = { this, ..., ..., [](on_body_complete_info info, parser_callback callback) { Parser * parser = (Parser *)callback.data; Local<Value> cb; Local<Context> context = parser->env()->GetContext(); Isolate * isolate = parser->env()->GetIsolate(); Local <String> key = newStringToLcal(isolate, "onBodyComplete"); parser->object()->Get(context, key).ToLocal(&cb); // 回调 JS 层 if (!cb.IsEmpty() && cb->IsFunction()) { Local<Value> argv[] = { newStringToLcal(isolate, info.body.c_str()) }; cb.As<v8::Function>()->Call(context, parser->object(), 1, argv); } }, }; httpparser = new HTTP_Parser(HTTP_REQUEST, callback); } void Parse(const char * data, size_t len); static void Parse(const FunctionCallbackInfo<Value>& args); static void New(const FunctionCallbackInfo<Value>& args); private: HTTP_Parser * httpparser; };C++ 模块到定义非常简单,只是对 HTTP_Parser 的封装,然后通过 V8 导出能力到 JS 层。
void No::HTTP::Init(Isolate* isolate, Local<Object> target) { Local<FunctionTemplate> parser = FunctionTemplate::New(isolate, No::HTTP::Parser::New); parser->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 的实现。香港云服务器
void No::HTTP::Parser::Parse(const FunctionCallbackInfo<Value>& args) { Parser * parser = (Parser *)unwrap(args.This()); Local<ArrayBuffer> arrayBuffer = args[0].As<ArrayBuffer>(); std::shared_ptr<BackingStore> backing = arrayBuffer->GetBackingStore(); const char * data = (const char * )backing->Data(); parser->Parse(data, strlen(data)); }Parse首先通过 args 拿到 C++ 的对象 Parser(熟悉 Node.js 的同学应该很容易明白这个处理方式)。接着调用 HTTP_Parser 的 parse 方法,在解析的过程中,llhttp 就会执行 HTTP_Parser 的回调, HTTP_Parser 就会执行 Parser 对象的回调,Parser 就会执行 JS 回调。比如解析完 body 后执行 JS 层回调。
[](on_body_complete_info info, parser_callback callback) { Parser * parser = (Parser *)callback.data; Local<Value> cb; Local<Context> context = parser->env()->GetContext(); Isolate * isolate = parser->env()->GetIsolate(); Local <String> key = newStringToLcal(isolate, "onBodyComplete"); parser->object()->Get(context, key).ToLocal(&cb); if (!cb.IsEmpty() && cb->IsFunction()) { Local<Value> argv[] = { newStringToLcal(isolate, info.body.c_str()) }; cb.As<v8::Function>()->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 层的封装。
class Server extends events { fd = -1; connections = 0; constructor(options = { }) { super(); const fd = 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++; const serverSocket = new ServerSocket({ fd: clientFd}); this.emit(connection, serverSocket); }); } }createServer 的时候会监听传入的地址,从而启动一个服务器,listen 回调执行说明有连接到来,我们新建一个 ServerSocket 对象表示和客户端通信的源码下载 Socket。并触发 connection 事件到上层。接着看 ServerSocket 的实现
class ServerSocket extends Socket { constructor(options = { }) { super(options); this.fd = options.fd; this.read(); } read() { const buffer = new ArrayBuffer(1024); tcp.read(this.fd, buffer, 0, (status) => { this.emit(data, buffer); this.read(); }) } }ServerSocket 的实现目前很简单,主要是读取数据并触发 data 事件,因为 TCP 只是负责数据传输,不负责数据解析。有了这个能力后,我们看看 http 层的实现。
function createServer(...arg) { return new Server(...arg);} class Server extends No.libs.tcp.Server { constructor(options = { }, cb) { super(options); this.options = options; if (typeof cb === function) { this.on(request, cb); } this.on(connection, (socket) => { new HTTPRequest({ socket, server: this}); }); } }http 模块继承于 tcp 模块,所以我们调用 http.createServer 的时候,会先执行 tcp 模块启动一个服务器,http 层监听 connection 事件等待连接到来,有连接到来时,http 创建一个 HTTPRequest 对象表示 http 请求。
class HTTPRequest extends No.libs.events { socket = null; httpparser = null; constructor({ socket, server}) { super(); this.server = server; this.socket = socket; this.httpparser = new HTTPParser(); 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. 保存底层的 socket 2. 新建一个 HTTPParser 解析 HTTP 协议。3. 监听 data 事件,收到 TCP 层数据后调用 HTTP 解析器解析。4. 注册 HTTP 解析的回调钩子,就是前面讲到的。等到解析完 HTTP header 后,也就是执行 onHeaderComplete 回调后,No.js 就会通过触发 request事件 回调业务层,也就是 createServer 传入的回调。业务层可以监听 HTTPRequest 的 data 事件,当 HTTP 请求有 body 数据时,就会注册 HTTPRequest 的 data 事件回调业务层。
4 总结
虽然目前只是粗糙地实现了 HTTP 模块,但实现的过程中,涉及到的内容还是挺多的,后面有时间再慢慢完善。有兴趣的同学可以到 https://github.com/theanarkh/No.js 了解。
很赞哦!(5)
相关文章
- 四、一定要仔细阅读细节
- 聊一聊SpringBoot项目热部署
- 使用Node.js还可以发邮件?
- 2020年,这个算法团队都干了啥?
- 用户邮箱的静态密码可能已被钓鱼和同一密码泄露。在没有收到安全警报的情况下,用户在适当的时间内不能更改密码。在此期间,攻击者可以随意输入帐户。启用辅助身份验证后,如果攻击者无法获取移动电话动态密码,他将无法进行身份验证。这样,除非用户的电子邮件密码和手机同时被盗,否则攻击者很难破解用户的邮箱。
- 鸿蒙HarmonyOS三方件开发指南-Updownfile
- 基础组件完善的今天,如何通过业务组件提效?
- 工作两三年了,整不明白架构图都画啥?
- 以上的就是为大家介绍的关于域名的详解域名注册:域名注册0
- 一篇文章带你了解JavaScript for循环
站长推荐
为什么说注册域名注意细节?哪些我们不能忽视?
别大意,你可能还没掌握好Java IO
用Python分析14425条死亡公司数据 看十年创业公司消亡史
用Python分析14425条死亡公司数据 看十年创业公司消亡史
当投资者经过第二阶段的认真学习之后又充满了信心,认为自己可以在市场上叱咤风云地大干一场了。但没想到“看花容易绣花难”,由于对理论知识不会灵活运用.从而失去灵活应变的本能,就经常会出现小赢大亏的局面,结果往往仍以失败告终。这使投资者很是困惑和痛苦,不知该如何办,甚至开始怀疑这个市场是不是不适合自己。在这种情况下,有的人选择了放弃,但有的意志坚定者则决定做最后的尝试。
VR,正在上演一出“风月宝鉴”
鸿蒙的JS开发部模式14:tabs组件通过Python远程服务构建项目一
如何在C#中使用 ArrayPool,MemoryPool