新闻详情

新闻详情

首页 / 资讯中心 / 详情

NodeMCU httpserver 模块实战指南:用 Lua 在 ESP8266 上实现 HTTP/1.1 服务器

发布时间:2026/9/28 20:57:03来源:尧图网络
NodeMCU httpserver 模块实战指南:用 Lua 在 ESP8266 上实现 HTTP/1.1 服务器
物联网嵌入式【免费下载链接】nodemcu-firmwareLua based interactive firmware for ESP8266, ESP8285 and ESP32项目地址https://gitcode.com/gh_mirrors/no/nodemcu-firmware点击查看免费下载导读NodeMCU 固件内置的net模块只提供最底层的 TCP 能力直接用它手写 HTTP 协议解析既繁琐又容易踩内存的坑。本文讲解的httpserverLua 模块源码位于 lua_modules/http/httpserver.lua官方文档见 docs/lua-modules/httpserver.md为 NodeMCU 提供了一套基于回调的 HTTP/1.1 服务器实现你只需调用httpserver.createServer(port, handler)并填写请求/响应回调就能在 ESP8266 上快速搭建一个可用的 Web 服务。读完本文你将掌握该模块的完整 APIreq请求对象与res响应对象、正确的回调编写方式、一个可直接运行的 Hello World 示例以及其底层的请求解析、chunked 分块传输和发送队列实现原理。一、模块定位纯 Lua 实现的 HTTP/1.1 服务器httpserver是一个完全用 Lua 编写的 HTTP 服务器模块最早由 Vladimir Dronnikovdvv于 2015-01-19 贡献目前也由该作者维护。它没有依赖固件内置的 C 模块而是直接构建在netTCP 服务器与fifosock发送队列之上通过回调函数把收到请求头收到请求体连接断开等事件暴露给业务代码。在 NodeMCU 官方文档中该模块被收录在Lua Modules一节与固件内置的 C 模块如http客户端是分开维护的它是可选的纯 Lua 模块需要把 httpserver.lua 上传到设备文件系统SPIFFS 或 LFS后使用而不是编译进固件。二、模块的加载与释放使用前先通过require加载模块httpserver require(httpserver)需要释放模块例如更新模块代码、释放内存时按如下顺序操作确保彻底卸载httpserver nil package.loaded[httpserver] nil先清空全局变量引用再把package.loaded中的缓存条目移除这样下次require才会重新加载最新版本的模块文件。三、启动服务器httpserver.createServer()createServer是模块暴露的唯一下层入口模块顶层的http表中只定义了createServer这一个字段调用后会立即开始监听端口并等待连接。语法与参数httpserver.createServer(port, handler(req, res))参数说明portHTTP 服务器监听的端口号。绝大多数 HTTP 服务器监听 80 端口ESP8266 上如果同时开了其他服务要注意端口冲突。handler回调函数每当收到 HTTP 请求时被调用。它接收两个参数req请求对象与res响应对象具体字段与方法见下文。返回值返回net.server子模块即net.createServer创建出来的服务器对象。也就是说createServer的返回值可以继续调用net模块的服务器方法比如srv:close()手动关闭服务。单实例限制与生命周期从源码 httpserver.lua 中可以看到createServer内部有一个关键设计-- NB: only one server at a time if srv then srv:close() end srv net.createServer(net.TCP, 15) srv:listen(port, http_handler(handler)) return srv同时只允许一个服务器实例模块用局部变量srv保存上一次创建的服务器再次调用createServer时会先把旧的服务器close()掉。所以重复调用createServer不会造成端口被占用但旧服务会被静默关闭多端口监听需要自行扩展。服务器由net.createServer(net.TCP, 15)创建其中的超时参数会传递给底层 TCP 服务器net.createServer的超时参数取值范围为 1~28 800 秒默认 30 秒详见 docs/modules/net.md意味着长时间不活跃的客户端连接会被底层自动断开。四、req 请求对象handler的第一个参数req是一个普通 Lua table其中既有数据字段也有可覆盖的回调字段字段类型/用途说明connnet.socket子模块底层 TCP 连接对象。不要在这个对象上调用:on或:send否则会破坏模块内部的解析状态机与发送队列所有响应都必须通过res对象完成。method字符串请求使用的方法例如GET、POST。url字符串请求的 URL 路径。onheader回调函数请求头解析完成时被调用函数签名onheader(self, name, value)。name永远是小写形式模块会把请求头名称统一转小写。ondata回调函数请求体数据到达时被调用函数签名ondata(self, chunk)。当全部请求体接收完毕时会额外调用一次chunk为nil的收尾回调。onheader根据请求头决定解析策略onheader在每一个请求头可用时立即触发头是逐行解析的解析到哪行回调到哪行。典型用途是根据content-type选择请求体解析器req.onheader function(self, name, value) print(H, name, value) -- 例如根据 content-type 决定 body 的解析方式 -- if name content-type then -- if value application/json then -- req.ondata function(self, chunk) ... end -- elseif value application/x-www-form-urlencoded then -- req.ondata function(self, chunk) ... end -- end -- end endondata处理请求体ondata在请求体数据分块到达时被反复调用注意它同样遵循 NodeMCU 网络模块最后一个 chunk 为nil的约定chunk nil表示整个请求体已经收完此时应当发送响应。示例 http-example.lua 正是这样判断请求体结束 → 回写响应的req.ondata function(self, chunk) print(B, chunk and #chunk, node.heap()) if not chunk then -- 请求体收完发送响应 res:send(nil, 200) res:send_header(Connection, close) res:send(Hello, world!\n) res:finish() end end关于conn字段的使用提醒官方文档特别强调DO NOT在req.conn上调用:on或:send。因为模块内部已经为这条连接注册了receive/disconnection/sent事件回调并用fifosock包装了发送通道直接操作conn会绕过解析器与队列导致响应乱序甚至丢数据。五、res 响应对象handler的第二个参数res提供三个方法用于向客户端写回 HTTP 响应res:send(data, [response_code])发送数据到客户端。res:send(data, [response_code])data要发送的数据可以为nil此时只发送状态行与响应头不发送 body 数据。response_codeHTTP 响应码如200默认或404。注意多次调用send时只有第一次传入的响应码会生效后续传入的码都会被忽略。res:send_header(header_name, header_data)发送 HTTP 响应头。res:send_header(header_name, header_data)必须在响应体开始发送之前调用。源码中send一旦发送了 body 数据就会把self.send_header置为nil之后send_header方法将不再可用调用会报错。模块内部在发送首个send时会自动附带Transfer-Encoding: chunked头详见下文chunked 分块传输你只需发送自定义头。res:finish([data[, response_code]])结束并关闭连接。res:finish([data[, response_code]])data可选结束时一并发送的数据。response_code可选响应码规则与send相同只有首次生效。调用finish后模块会写入 chunked 编码的结束标记0\r\n\r\n并在全部数据真正发送完毕后关闭底层连接、清理事件回调。一个最简单的响应可以只写一行res:finish(Hello, world!)finish内部会先走一遍send因此res:finish(Salut, monde!)这种写法等价于发送数据 结束连接适合快速返回小体积响应。六、完整可运行示例仓库自带的 http-example.lua 是一个完整的 Hello World 服务器。把它上传到设备后运行下面的代码即可启动require(httpserver).createServer(80, function(req, res) -- 分析请求方法与 URL print(R, req.method, req.url, node.heap()) -- 注册请求头回调如果有请求头 req.onheader function(self, name, value) -- luacheck: ignore print(H, name, value) -- 可根据 content-type 选择 body 解析方式 end -- 注册请求体回调如果有请求体 req.ondata function(self, chunk) -- luacheck: ignore print(B, chunk and #chunk, node.heap()) if not chunk then -- 请求体收完回写响应 res:send(nil, 200) res:send_header(Connection, close) res:send(Hello, world!\n) res:finish() end end -- 或者不等待请求体直接返回 --res:finish(Hello, world!) --res:finish(Salut, monde!) end)运行流程梳理createServer(80, handler)创建 TCP 服务器并监听 80 端口浏览器/客户端发起请求TCP 连接建立模块开始逐行解析解析到请求行如GET / HTTP/1.1后构造req、res对象并调用handler(req, res)此时req.method、req.url已可用每解析到一个请求头触发一次req.onheader请求头解析完毕后续接收到的数据全部作为请求体交给req.ondata直到chunk nil表示收尾业务代码在收尾回调中调用res:send/res:send_header/res:finish回写响应模块负责把数据按 chunked 编码发出并关闭连接。七、底层实现剖析7.1 请求解析状态机http_handler(handler)lua_modules/http/httpserver.lua把一条 TCP 连接包装成一个逐行解析的状态机请求行解析用模式^([A-Z]) (.-) HTTP/1.1$从第一行提取method与url。从源码注释NB: just version 1.1 assumed可以推断该模块假定客户端都使用 HTTP/1.1 请求行如果请求行不是这个格式例如HTTP/1.0将不会被识别为合法请求。请求头解析用模式^([%w-]):%s*(.)解析名称: 值形式并把名称lower()转小写后交给onheader。请求头结束判定遇到空行表示头部结束。此时模块重新挂载receive回调为ondata并把当前缓冲区剩余部分作为请求体的第一个 chunk 喂给ondata——这是典型的边接收边解析做法避免数据滞留在 Lua 字符串拼接中。7.2 Content-Length 与请求体结束信号解析头部期间模块会特别关注两个头content-length记录到局部变量cnt_len用于判断请求体何时接收完毕expect: 100-continue模块会自动回写HTTP/1.1 100 Continue\r\n配合客户端的分段上传协议。请求体处理逻辑在ondata内部每收到一个 chunk累加body_len当body_len cnt_len时再调用一次req:ondata()不带参数等价于chunk nil通知业务代码请求体结束。因此即使客户端没有发content-length头只要连接关闭或数据收尾你依然会在ondata中收到一次nil收尾回调。7.3 chunked 分块传输编码res:send在首次发送时自动输出HTTP/1.1 200 OK\r\n Transfer-Encoding: chunked\r\n ...你通过 send_header 添加的头... \r\n随后每个data都以%X\r\n形式输出十六进制的数据长度后跟数据与\r\nres:finish最后输出0\r\n\r\n作为 chunked 编码的终止标记httpserver.lua 中的csend(0\r\n\r\n)。选择 chunked 编码是为了不依赖Content-Length头——在 ESP8266 这种小内存设备上业务代码可以一边生成数据一边发送无需预先知道总长度。需要留意的实现限制源码中均有 TODO 注释佐证状态行固定输出HTTP/1.1 code OK\r\n没有真实的 HTTP 状态码/名称表404 等状态也会附带OK文本不会自动发送Server:、Date:等标准响应头响应体一旦开始发送不允许再追加响应头。7.4 fifosock 发送队列模块用(require fifosock).wrap(conn)把底层 socket 的send包装成csendfifosock.lua。fifosock是一个两段式 FIFO 发送队列它会合并小字符串以减少 TCP 包数量、把大字符串切块并在 socket 的sent事件驱动下按序发送同时支持把函数排入队列作为发送完成回调。这带来两个实际影响res:send/res:finish只是把数据写进队列真正的网络发送由sent事件异步驱动所以返回后数据不一定已到达客户端httpserver.lua中的cfini通过csend(function() conn:close() ... end)的方式确保所有排队的响应数据都发送完毕后才关闭连接避免响应被截断。同时由于fifosock会在 Lua registry 中形成 socket 与包装器的循环引用详见 docs/lua-modules/fifosock.md模块在断开回调ondisconnect中主动清空三个事件回调并调用collectgarbage(collect)来回收内存——这也是为什么你不应该在req.conn上自行注册回调的原因之一。7.5 内存与连接清理每次连接断开时ondisconnect会执行connection:on(receive, nil) connection:on(disconnection, nil) connection:on(sent, nil) collectgarbage(collect)把三个事件回调全部摘除再主动触发一次完整 GC。配合net.createServer的 15 秒超时参数httpserver.lua 中传入长时间无活动的连接会被底层自动回收。这保证了在持续请求的场景下模块不会因为积累废弃连接和 Lua 字符串缓冲区而耗尽 ESP8266 稀缺的 RAM。你可以在ondata/onheader回调中用node.heap()观察剩余堆内存验证内存回收效果示例代码中已有该打印。八、实践要点与注意事项端口选择通常监听 80 端口方便浏览器直接访问若设备上同时运行了固件内置http模块或其他服务注意避免端口冲突。不要直接操作req.connconn仅供读取信息:on/:send一律通过res完成否则会破坏解析状态机与发送队列。注意请求行格式模块按HTTP/1.1请求行解析标准浏览器与 curl 均满足该格式自研客户端务必使用 HTTP/1.1。响应头必须在 body 之前发送send_header在第一次send之后即变为nil响应码也只有第一次生效。善用finish如果响应体可以在请求体到达前就生成如静态内容、简单的状态页直接调用res:finish(data, code)即可不必等待ondata的收尾回调。内存敏感场景ESP8266 可用 RAM 有限建议在回调中监控node.heap()返回大响应时尽量分块send让fifosock队列异步发送避免一次性构造超长字符串。九、延伸阅读模块官方文档本指南的 API 依据docs/lua-modules/httpserver.md模块实现源码lua_modules/http/httpserver.lua完整示例lua_modules/http/http-example.lua底层依赖 fifosock 模块文档与源码docs/lua-modules/fifosock.md、lua_modules/fifo/fifosock.lua底层 TCP 服务器net.createServer的 C 实现与文档app/modules/net.c、docs/modules/net.md赞分享物联网嵌入式【免费下载链接】nodemcu-firmwareLua based interactive firmware for ESP8266, ESP8285 and ESP32项目地址https://gitcode.com/gh_mirrors/no/nodemcu-firmware点击查看免费下载相关推荐NodeMCU HTTP 服务器 Lua 模块httpserver完整实战指南API 详解与源码级原理剖析NodeMCU HTTP 服务器 Lua 模块httpserver完整实战指南API 详解与源码级原理剖析 导读 本文围绕 NodeMCU 固件仓库中的物联网嵌入式NodeMCU 固件的 ESP8266 FTP 服务器 Lua 模块实战指南LFS 加载NodeMCU 固件的 ESP8266 FTP 服务器 Lua 模块实战指南LFS 加载 导读 ftpserver.lua 是 NodeMCU 固件仓库中一物联网嵌入式NodeMCU ucg 模块实战用 Lua 驱动彩色 TFT 屏ESP8266 / ESP32NodeMCU ucg 模块实战用 Lua 驱动彩色 TFT 屏ESP8266 / ESP32 ucg 是 NodeMCU 固件中面向彩色 TFT 显示屏物联网嵌入式创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

自建微服务SDK:从调用乱象到三行接入的工程实践 2026/9/28 21:45:18

自建微服务SDK:从调用乱象到三行接入的工程实践

一年前,我们平台组最大的工作量不是写新功能,而是反复帮业务服务“擦屁股”。同一个第三方 API,有人用requests裸调,有人走内部网关,有人干脆把密钥写在配置中心里每次现读。最典型的一次,某家供应商调整了…

阅读更多 →
Superpowers助力Codex:解决Java多模块重构的工程记忆问题 2026/9/28 21:45:18

Superpowers助力Codex:解决Java多模块重构的工程记忆问题

先说一个很具体的场景。上个月我接手了一个跑了八年的Java老服务,四十多个模块,构建一次要三分半钟。我把一段重构需求原样丢给Codex,它很快给出了修改方案,编译也过了,结果上线前review才发现它改了一个上游接口&…

阅读更多 →
一条命令存档整站:如何把任意网站完整下载到本地离线使用 2026/9/28 21:44:58

一条命令存档整站:如何把任意网站完整下载到本地离线使用

一条命令存档整站:如何把任意网站完整下载到本地离线使用 【免费下载链接】Website-downloader 💡 Download the complete source code of any website (including all assets). [ Javascripts, Stylesheets, Images ] using Node.js 项目地址: https…

阅读更多 →
ESP32S3外挂W5500以太网方案:硬件设计、驱动移植与TCP通信实战 2026/9/28 21:44:51

ESP32S3外挂W5500以太网方案:硬件设计、驱动移植与TCP通信实战

1. 为什么要在ESP32S3上外挂W55001.1 从一次现场掉线说起前阵子帮朋友处理一个工业数据采集的活儿,设备装在配电柜里,用ESP32S3做主板,通过Wi-Fi把采集到的电流、温度数据往服务器上传。实验室里跑了一周都挺正常,结果到了现场第三…

阅读更多 →
金融服务后端搭建指南:账户模型、幂等和资金安全实践 2026/9/28 21:44:24

金融服务后端搭建指南:账户模型、幂等和资金安全实践

最开始接到「financial-services」这个项目名的时候,我其实挺头疼的。这个仓库名一看就是金融向的服务,但金融服务的边界太大:账户、支付、转账、理财、贷款、风控、对账……随便拎一个出来都是能做一年的模块。更麻烦的是,金融项…

阅读更多 →
Agent-Native架构实战:从AI外挂到智能体原生的系统重构指南 2026/9/28 21:44:17

Agent-Native架构实战:从AI外挂到智能体原生的系统重构指南

最近“agent-native”被频繁提及,技术圈在讨论,产品经理在写PPT,连不少VC也把它当成AI应用的下一站。但你要是追着问一句“我现在做的东西到底算不算agent-native”,很多人其实是答不上来的。我今年带了两个团队把一套B端系统从传…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉