Skip to content

Commit ca4fb20

Browse files
HHHartmannmarcoskirsch
authored andcommitted
Serve static pages efficiently. Fix #53 (#118)
* 1st draft to serve static files faster * Allow serving 5 images in a page the 6th image cannot be served as the esp does not open more than 5 connections at the same time * win the prize * Update comments
1 parent 9f4d7a9 commit ca4fb20

File tree

4 files changed

+95
-25
lines changed

4 files changed

+95
-25
lines changed

http/cars.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ return function (connection, req, args)
3131
It works with three embedded images of cars, but the server crashes with four. Select the number of cars you want to see below.<br>
3232
Whoever manages to modify nodemcu-httpserver to load all four images without crashing wins a prize!
3333
</p>
34+
<p>
35+
OK I guess I win the prize, as now you can load five cars.<br>
36+
Cheers HHHartmann
37+
</p>
3438
<p>
3539
choose: <a href="?n=1">show one car</a>
3640
<a href="?n=2">show two cars</a>

httpserver-buffer.lua

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
-- httpserver-buffer
2+
-- Part of nodemcu-httpserver, provides a buffer that behaves like a connection object
3+
-- that can handle multiple consecutive send() calls, and buffers small payloads up to 1400 bytes.
4+
-- This is primarily user to collect the send requests done by the head script.
5+
-- The owner is responsible to call getBuffer and send its result
6+
-- Author: Gregor Hartmann
7+
8+
local Buffer = {}
9+
10+
-- parameter is the nodemcu-firmware connection
11+
function Buffer:new()
12+
local newInstance = {}
13+
newInstance.size = 0
14+
newInstance.data = {}
15+
16+
-- Returns true if there was any data to be sent.
17+
function newInstance:getBuffer()
18+
local buffer = table.concat(self.data, "")
19+
self.data = {}
20+
self.size = 0
21+
return buffer
22+
end
23+
24+
function newInstance:getpeer()
25+
return "no peer"
26+
end
27+
28+
function newInstance:send(payload)
29+
local flushThreshold = 1400
30+
if (not payload) then print("nop payload") end
31+
local newSize = self.size + payload:len()
32+
if newSize >= flushThreshold then
33+
print("Buffer is full. Cutting off "..newSize-flushThreshold.." chars")
34+
--STEP1: cut out piece from payload to complete threshold bytes in table
35+
local pieceSize = flushThreshold - self.size
36+
if pieceSize then
37+
payload = payload:sub(1, pieceSize)
38+
end
39+
end
40+
table.insert(self.data, payload)
41+
self.size = self.size + #payload
42+
end
43+
44+
return newInstance
45+
46+
end
47+
48+
return Buffer

httpserver-static.lua

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,13 @@
11
-- httpserver-static.lua
22
-- Part of nodemcu-httpserver, handles sending static files to client.
3-
-- Author: Marcos Kirsch
3+
-- Author: Gregor Hartmann
44

55
return function (connection, req, args)
6-
dofile("httpserver-header.lc")(connection, 200, args.ext, args.isGzipped)
7-
-- Send file in little chunks
8-
local bytesRemaining = file.list()[args.file]
9-
-- Chunks larger than 1024 don't work.
10-
-- https://github.com/nodemcu/nodemcu-firmware/issues/1075
11-
local chunkSize = 1024
12-
local fileHandle = file.open(args.file)
13-
while bytesRemaining > 0 do
14-
local bytesToRead = 0
15-
if bytesRemaining > chunkSize then bytesToRead = chunkSize else bytesToRead = bytesRemaining end
16-
local chunk = fileHandle:read(bytesToRead)
17-
connection:send(chunk)
18-
bytesRemaining = bytesRemaining - #chunk
19-
--print(args.file .. ": Sent "..#chunk.. " bytes, " .. bytesRemaining .. " to go.")
20-
chunk = nil
21-
collectgarbage()
22-
end
23-
-- print("Finished sending: ", args.file)
24-
fileHandle:close()
25-
fileHandle = nil
26-
collectgarbage()
6+
7+
local buffer = dofile("httpserver-buffer.lc"):new()
8+
dofile("httpserver-header.lc")(buffer, req.code or 200, args.ext, args.isGzipped)
9+
-- Send header and return fileInfo
10+
connection:send(buffer:getBuffer())
11+
12+
return { file = args.file, sent = 0}
2713
end

httpserver.lua

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ return function (port)
1313
-- We do it in a separate thread because we need to send in little chunks and wait for the onSent event
1414
-- before we can send more, or we risk overflowing the mcu's buffer.
1515
local connectionThread
16+
local fileInfo
1617

1718
local allowStatic = {GET=true, HEAD=true, POST=false, PUT=false, DELETE=false, TRACE=false, OPTIONS=false, CONNECT=false, PATCH=false}
1819

@@ -26,6 +27,10 @@ return function (port)
2627
end
2728
end
2829

30+
local function startServingStatic(connection, req, args)
31+
fileInfo = dofile("httpserver-static.lc")(connection, req, args)
32+
end
33+
2934
local function startServing(fileServeFunction, connection, req, args)
3035
connectionThread = coroutine.create(function(fileServeFunction, bufferedConnection, req, args)
3136
fileServeFunction(bufferedConnection, req, args)
@@ -40,6 +45,7 @@ return function (port)
4045

4146
local BufferedConnectionClass = dofile("httpserver-connection.lc")
4247
local bufferedConnection = BufferedConnectionClass:new(connection)
48+
BufferedConnectionClass = nil
4349
local status, err = coroutine.resume(connectionThread, fileServeFunction, bufferedConnection, req, args)
4450
if not status then
4551
log(connection, "Error: "..err)
@@ -50,7 +56,7 @@ return function (port)
5056
end
5157
end
5258

53-
local function handleRequest(connection, req)
59+
local function handleRequest(connection, req, handleError)
5460
collectgarbage()
5561
local method = req.method
5662
local uri = req.uri
@@ -84,7 +90,8 @@ return function (port)
8490
else
8591
if allowStatic[method] then
8692
uri.args = {file = uri.file, ext = uri.ext, isGzipped = uri.isGzipped}
87-
fileServeFunction = dofile("httpserver-static.lc")
93+
startServingStatic(connection, req, uri.args)
94+
return
8895
else
8996
uri.args = {code = 405, errorString = "Method not supported", logFunction = log}
9097
fileServeFunction = dofile("httpserver-error.lc")
@@ -95,7 +102,7 @@ return function (port)
95102
end
96103

97104
local function onReceive(connection, payload)
98-
collectgarbage()
105+
-- collectgarbage()
99106
local conf = dofile("httpserver-conf.lc")
100107
local auth
101108
local user = "Anonymous"
@@ -162,6 +169,27 @@ return function (port)
162169
connectionThread = nil
163170
collectgarbage()
164171
end
172+
elseif fileInfo then
173+
local fileSize = file.list()[fileInfo.file]
174+
-- Chunks larger than 1024 don't work.
175+
-- https://github.com/nodemcu/nodemcu-firmware/issues/1075
176+
local chunkSize = 512
177+
local fileHandle = file.open(fileInfo.file)
178+
if fileSize > fileInfo.sent then
179+
fileHandle:seek("set", fileInfo.sent)
180+
local chunk = fileHandle:read(chunkSize)
181+
fileHandle:close()
182+
fileHandle = nil
183+
fileInfo.sent = fileInfo.sent + #chunk
184+
connection:send(chunk)
185+
-- print(fileInfo.file .. ": Sent "..#chunk.. " bytes, " .. fileSize - fileInfo.sent .. " to go.")
186+
chunk = nil
187+
else
188+
log(connection, "closing connetion", "Finished sending: "..fileInfo.file)
189+
connection:close()
190+
fileInfo = nil
191+
end
192+
collectgarbage()
165193
end
166194
end
167195

@@ -172,6 +200,10 @@ return function (port)
172200
connectionThread = nil
173201
collectgarbage()
174202
end
203+
if fileInfo then
204+
fileInfo = nil
205+
collectgarbage()
206+
end
175207
end
176208

177209
connection:on("receive", onReceive)

0 commit comments

Comments
 (0)