I built a new http server module using with LFS.
There are 5 files need to be write into "lfs.img".
I don't have premission to push file so I put it here.
Memory leak detected when a client(browser) pushed a request but I don't know how to fix it.
-- _httpCreateResponse.lua :
return function(sck, req)
local r = {};
r.socket = sck;
r.req = req;
r.headers = {};
r.isHeaderWrote = nil;
r.queue = {};
r.events = {};
r.finished = nil;
r._send = function(data) r.socket:send(data); end
r.write = function(data) LFS._httpResponseWrite()(r, data); end
r.on = function(event, fn) r.events[event] = fn; end
r.emit = function(event, data)
local fn = r.events[event];
if fn then fn(data); end
end
r.writeFile = function(fileName, length)
LFS._httpResponseWriteFile()(r, fileName, length);
end
r.setHeader = function(key, value) r.headers[key] = value; end
r.finish = function(data)
r.finished = true;
r.write(data);
r.dispose();
end
r.dispose = function()
-- print(#self.queue, self.finished)
if #r.queue <= 0 and r.finished then
-- print("disposing")
-- release the callback of "sent"
r.socket:on("sent",nil);
r.req = nil;
-- self.socket:close will thore an error to raise esp8266 restart
-- so use pcall to execute
local function f() r.socket:close(); end
pcall(f);
-- mark all tables as nil manually
-- f, res.socket = nil, nil;
-- res, req, conn = nil, nil, nil;
gc();
end
end
sck:on("sent",function ()
r.emit("drain"); -- emit the event to outer
if #r.queue > 0 then
r._send(table.remove(r.queue, 1)); -- send the item in the fifo
else
r.dispose(); -- release resources
end
end)
return r;
end
-- _httpReceiveCallback.lua :
return function(sck, data, sockets, fn)
-- to match method, path and host information from raw data
local _, _, method, url = data:find("^(%u+)%s+(.+)%s+HTTP/1.1");
-- find is a same socket exists
local obj = sockets[sck];
if obj then
if obj.method == "GET" and obj.url == url then
-- close last socket(for saving memory)
-- if request from same client and has same url
-- typical view is a client press F5 very fast
-- if don't do this esp8266 will panic and reboot
-- and that is unacceptable
sck:close();
sockets[sck] = nil;
elseif obj.method == "POST" then
-- if socket exists but there are plenty of data to send from
-- browser to esp8266
-- then just push the data
obj.res.emit("data", data);
obj.postOffset = obj.postOffset + #data;
if obj.postOffset >= obj.postLen then
obj.res.emit("close", obj.postLen);
sockets[sck] = nil;
end
return;
end
else
-- this client is first time pushed request
obj = {method = method, url = url};
-- copy the ref
sockets[sck] = obj;
end
-- if the code running to headers
-- that means this socket has different url or the first time push the request
local h = LFS.split()(data, "\r\n");
local headers = {};
for _, value in pairs(h) do
if value:find("%S+") then
local _, _, key, value = value:find("(.+):%s+(.+)");
if key then
table.insert(headers, {key = key, value = value});
end
end
end
-- save content length to objects
if method == "POST" then
for _, value in pairs(headers) do
if value.key:find("Content-length") then
obj.postLen = tonumber(value.value);
obj.postOffset = 0;
end
end
if obj.postLen == nil then
sck:close();
obj = nil;
end
end
-- renew the request objects
local req = {
url = url, -- the path that browser request
method = method, -- the method
headers = headers
};
-- save request
obj.req = req;
local res = LFS._httpCreateResponse()(sck, req);
obj.res = res;
-- execute the callback
fn(req, res);
end
-- _httpResponseWrite.lua :
return function(res, data)
if not res.isHeaderWrote then
-- write the http headers before body
res.isHeaderWrote = true;
-- add all headers to the send queue
for _, value in ipairs(res.headers) do
table.insert(res.queue,
(value.name .. ": " .. value.value .. "\r\n"));
end
-- add endl
table.insert(res.queue, "\r\n");
-- send the "head" of the headers
local head = "HTTP/1.1 " .. (res.statusCode or "200") .. " OK\r\n" ..
"Host: " .. (res.req.host or "NodeMCU") .. "\r\n";
res._send(head);
end
if data then -- enqueue the data
table.insert(res.queue, data);
end
end
-- _httpResponseWriteFile.lua :
return function(r, fileName, length)
if r.f then
gc();
local d = r.f.read(length or 128);
if d then
r.write(d);
else
r.f.close();
r.f = nil;
r.finish();
r.on("drain",nil);
end
return;
end
if file.exists(fileName) then
local function rs(value) r.setHeader("Content-type", value); end
local function nf(pattern) return fileName:find(pattern); end
-- some common file type
if nf(".+%.html") then
rs("text/html");
elseif nf(".+%.js") then
rs("application/x-javascript");
elseif nf(".+%.jpg") then
rs("image/jpeg");
elseif nf(".+%.jpeg") then
rs("image/jpeg");
elseif nf(".+%.png") then
rs("image/png");
elseif nf(".+%.gif") then
rs("image/gif");
elseif nf(".+%.xml") then
rs("text/xml");
elseif nf(".+%.json") then
rs("text/json");
elseif nf(".+%.pdf") then
rs("application/pdf");
elseif nf(".+%.bin") then
rs("application/octet-stream");
elseif nf(".+%.exe") then
rs("application/octet-stream");
elseif nf(".+%.mp3") then
rs("audio/mp3");
else
rs("text/plain");
end
r.setHeader("Connection", "close");
r.f = file.open(fileName, "r");
r.on("drain", function() r.writeFile(); end)
r.writeFile();
end
end
-- httpCreateServer.lua :
return function(timeout)
timeout = timeout or 28000;
local srv = {server = net.createServer(net.TCP, (60))};
local sockets = {};
-- to create function to start to listen
srv.listen = function(port, fn)
-- default port is 80
port = port or 80;
srv.server:listen(port, function(conn)
-- bind receive function
conn:on("receive", function(sck, data)
LFS._httpReceiveCallback()(sck, data, sockets, fn);
end);
conn:on("disconnection", function(socket)
if sockets[socket] then
sockets[socket].res.emit("close");
sockets[socket] = nil;
end
end)
end);
end
return srv;
end
-- split.lua :
return function(d, c)
local t = {};
for item in string.gmatch(d, "(.-)" .. c) do table.insert(t, item); end
return t;
end
-- use :
LFS = node.LFS;
local server = LFS.httpCreateServer()(60);
server.listen(80, function(req, res)
if req.url == "/" then req.url = "/index.html"; end
-- route
if req.url:find("^/([^/]+)$") then
local _, _, name = req.url:find("/(.+)");
if file.exists(name) then
res.writeFile(name);
else
res.finish();
gc();
end
else
res.finish("Hello NodeMCU");
-- ...
end
local data = "";
res.on("data", function(d) data = data .. d; end)
res.on("close", function() print(data); end)
end)
A new HttpServer
I built a new http server module using with LFS. There are 5 files need to be write into "lfs.img". I don't have premission to push file so I put it here.
Memory leak detected when a client(browser) pushed a request but I don't know how to fix it.
ENV: NodeMCU 3.0.0.0 branch: release commit: d4ae3c364bd8ae3ded8b77d35745b7f07879f5f9 release: 3.0.0-release_20210201 +1 release DTS: 202105102018 SSL: false build type: float LFS: 0x40000 bytes total capacity modules: adc,coap,crypto,encoder,file,gpio,http,i2c,mdns,net,node,pwm,rtcfifo,rtcmem,rtctime,sjson,sntp,spi,tmr,uart,websocket,wifi build 2021-06-01 00:56 powered by Lua 5.3.5 on SDK 3.0.1-dev(fce080e)