Skip to content

Commit 8384606

Browse files
committed
html tables, htm headers, fragmentation logic, file.exists, refactoring
Implemented html tables in webgui forms for better alignment Replaced some string headers with dofile("httpserver-header") Implemented fragmentation logic for bufferedConnection:send() replaced file.open()-file.close() pairs with file.exists() Refactoring of some code and minor cleanups
1 parent e924fe5 commit 8384606

14 files changed

+191
-127
lines changed

compile.lua

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55
return function(filename)
66
local function compileAndRemoveIfNeeded(f)
7-
if file.open(f) then
8-
file.close()
7+
if file.exists(f) then
98
print('Compiling:', f)
109
tmr.wdclr()
1110
node.compile(f)

confload.lua

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
-- Author: Daniel Salazar
2+
-- Reads and returns a something.lc conf file.
3+
-- If default conf file doesn't exist (something-default.lc), it gets created by *makedefault.lc
4+
-- If conf file doesn't exist (something.lc), it gets created from the default.
5+
-- Requirements: the conf file must have 2 scripts implemented, which must
6+
-- follow a naming convention
7+
-- somethingmakedefault.lc: script that generates and returns a default conf object
8+
-- somethingwrite.lc: script that takes a conf obj and writes it to a *.lua file (uncompiled)
9+
-- In addition, a compile.lc script must exist, which compiles the .lua conf to .lc.
10+
-- Example:
11+
-- wifiConf = dofile("wifi-conf.lc")
12+
-- httpservConf = dofile("httpserver-conf.lc")
13+
14+
-- fnConf: conf file name, must be of form "something.lc"
15+
-- return: a conf object
16+
return function(fnConf)
17+
local dot = fnConf:find("%.")
18+
assert(dot ~= nil, "confload: conf file name doesn't have extension")
19+
local fnBody = fnConf:sub(1, dot-1)
20+
local fnExt = fnConf:sub(dot+1, -1)
21+
22+
local conf
23+
24+
--check if the default config file exists, if not create it
25+
if not file.exists(fnBody.."-default."..fnExt) then
26+
print(fnBody.."-default."..fnExt.." (default config) not found, creating...")
27+
conf = dofile(fnBody.."makedefault.lc")
28+
dofile(fnBody.."write.lc")(conf, fnBody.."-default.lua")
29+
dofile("compile.lc")(fnBody.."-default.lua")
30+
end
31+
32+
--check if the user config file exists, if not save the default config to it
33+
if not file.exists(fnConf) then
34+
print(fnConf.." (user config) not found, creating from default...")
35+
conf = dofile(fnBody.."makedefault.lc")
36+
dofile(fnBody.."write.lc")(conf, fnBody..".lua")
37+
dofile("compile.lc")(fnBody..".lua")
38+
end
39+
40+
conf = dofile(fnConf)
41+
return conf
42+
end

http/file_list.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
return function (connection, req, args)
2-
connection:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nCache-Control: private, no-store\r\n\r\n")
2+
--connection:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nCache-Control: private, no-store\r\n\r\n")
3+
dofile("httpserver-header.lc")(connection, 200, "html")
34
connection:send('<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Server File Listing</title></head>')
45
connection:send('<body>')
56
connection:send('<h1>Server File Listing</h1>')

http/node_info.lua

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1-
local function sendHeader(connection)
2-
connection:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nCache-Control: private, no-store\r\n\r\n")
1+
--local function sendHeader(connection)
2+
--connection:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nCache-Control: private, no-store\r\n\r\n")
33

4-
end
4+
--end
55

66
local function sendAttr(connection, attr, val)
77
if val then
8-
connection:send("<li><b>".. attr .. ":</b> " .. val .. "<br></li>\n")
8+
connection:send("<tr><td><b>".. attr .. ":</b></td><td>" .. val .. "</td></tr>\n")
99
end
1010
end
1111

1212
return function (connection, req, args)
1313
collectgarbage()
14-
sendHeader(connection)
15-
connection:send('<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Node info</title></head><body><h1>Node info</h1><ul>')
14+
--sendHeader(connection)
15+
dofile("httpserver-header.lc")(connection, 200, "html")
16+
connection:send('<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Node info</title></head><body><h1>Node info</h1><table>')
1617
local majorVer, minorVer, devVer, chipid, flashid, flashsize, flashmode, flashspeed = node.info();
1718
sendAttr(connection, "NodeMCU version" , majorVer.."."..minorVer.."."..devVer)
1819
sendAttr(connection, "Chip id" , chipid)
@@ -49,5 +50,5 @@ return function (connection, req, args)
4950
end
5051
sendAttr(connection, 'Station MAC' , wifi.sta.getmac())
5152

52-
connection:send('</ul></body></html>')
53+
connection:send('</table></body></html>')
5354
end

httpserver-basicauth.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ function basicAuth.authenticate(header, conf)
1616
local credentials = dofile("httpserver-b64decode.lc")(credentials_enc)
1717
local user, pwd = credentials:match("^(.*):(.*)$")
1818
if user ~= conf.auth.user or pwd ~= conf.auth.password then
19+
print("httpserver-basicauth: User \"" .. user .. "\": Access denied.")
1920
return nil
2021
end
21-
print("httpserver-basicauth: User \"" .. user .. "\" authenticated.")
22+
print("httpserver-basicauth: User \"" .. user .. "\": Authenticated.")
2223
return user
2324
end
2425

httpserver-header.lua

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,19 @@ return function (connection, code, extension, gzip)
1212
end
1313

1414
local function getMimeType(ext)
15-
local gzip = false
1615
-- A few MIME types. Keep list short. If you need something that is missing, let's add it.
17-
local mt = {css = "text/css", gif = "image/gif", html = "text/html", ico = "image/x-icon", jpeg = "image/jpeg", jpg = "image/jpeg", js = "application/javascript", json = "application/json", png = "image/png", xml = "text/xml"}
18-
local contentType = {}
19-
if mt[ext] then
20-
contentType = mt[ext] --
21-
else
22-
contentType = "text/plain"
23-
end
24-
return {contentType = contentType, gzip = gzip}
16+
local mt = {css = "text/css", gif = "image/gif", html = "text/html", ico = "image/x-icon", jpeg = "image/jpeg", jpg = "image/jpeg", js = "application/javascript", json = "application/json", png = "image/png", xml = "text/xml"}
17+
local contentType = {}
18+
if mt[ext] then
19+
return mt[ext] --
20+
end
21+
22+
return "text/plain"
2523
end
2624

2725
local mimeType = getMimeType(extension)
2826

29-
connection:send("HTTP/1.0 " .. code .. " " .. getHTTPStatusString(code) .. "\r\nServer: nodemcu-httpserver\r\nContent-Type: " .. mimeType["contentType"] .. "\r\n")
27+
connection:send("HTTP/1.0 " .. code .. " " .. getHTTPStatusString(code) .. "\r\nServer: nodemcu-httpserver\r\nContent-Type: " .. mimeType .. "\r\n")
3028
if gzip then
3129
connection:send("Cache-Control: max-age=2592000\r\n")
3230
connection:send("Content-Encoding: gzip\r\n")

httpserver.lua

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -21,49 +21,80 @@ return function (conn)
2121
local bufferedConnection = {}
2222
function bufferedConnection:flush()
2323
if self.size > 0 then
24+
--uart.write(0, "httpserver: buff:flush(): size="..self.size.."\n")
2425
connection:send(table.concat(self.data, ""))
2526
self.data = {}
26-
self.size = 0
27+
self.size = 0
28+
collectgarbage()
2729
return true
2830
end
2931
return false
3032
end
33+
3134
function bufferedConnection:send(payload)
32-
local l = payload:len()
33-
if l + self.size > 1000 then
35+
local flushthreshold = 1400
36+
37+
38+
local newsize = self.size + payload:len()
39+
while newsize > flushthreshold do
40+
--STEP1: cut out piece from payload to complete threshold bytes in table
41+
local piecesize = flushthreshold - self.size
42+
local piece = payload:sub(1, piecesize)
43+
payload = payload:sub(piecesize + 1, -1)
44+
--STEP2: insert piece into table
45+
table.insert(self.data, piece)
46+
self.size = self.size + piecesize --size should be same as flushthreshold
47+
--STEP3: flush entire table
3448
if self:flush() then
35-
coroutine.yield()
49+
coroutine.yield()
3650
end
51+
--at this point, size should be 0, because the table was just flushed
52+
newsize = self.size + payload:len()
3753
end
38-
if l > 800 then
39-
connection:send(payload)
40-
coroutine.yield()
41-
else
54+
55+
--at this point, whatever is left in payload should be <= flushthreshold
56+
local plen = payload:len()
57+
if plen == flushthreshold then
58+
--case 1: what is left in payload is exactly flushthreshold bytes (boundary case), so flush it
59+
table.insert(self.data, payload)
60+
self.size = self.size + plen
61+
if self:flush() then
62+
coroutine.yield()
63+
end
64+
elseif payload:len() then
65+
--case 2: what is left in payload is less than flushthreshold, so just leave it in the table
4266
table.insert(self.data, payload)
43-
self.size = self.size + l
67+
self.size = self.size + plen
68+
--else, case 3: nothing left in payload, so do nothing
4469
end
4570
end
71+
4672
bufferedConnection.size = 0
4773
bufferedConnection.data = {}
4874

4975
connectionThread = coroutine.create(
5076
function(fileServeFunction, bconnection, req, args)
77+
--uart.write(0, "httpserver: coroutine(): about to fileServeFunction \n")
5178
fileServeFunction(bconnection, req, args)
5279
if not bconnection:flush() then
5380
connection:close()
5481
connectionThread = nil
5582
end
83+
--uart.write(0, "httpserver: coroutine(): end\n")
5684
end
5785
)
5886

5987
local status, err = coroutine.resume(connectionThread, fileServeFunction, bufferedConnection, req, args)
6088
if not status then
61-
print(err)
89+
print("Error: "..err)
6290
end
6391
collectgarbage()
6492
end
6593

94+
6695
local function onRequest(connection, req)
96+
--uart.write(0, "httpserver: onRequest()\n")
97+
6798
collectgarbage()
6899
local method = req.method
69100
local uri = req.uri
@@ -76,16 +107,14 @@ return function (conn)
76107
uri.args = {code = 400, errorString = "Bad Request"}
77108
fileServeFunction = dofile("httpserver-error.lc")
78109
else
79-
local fileExists = file.open(uri.file, "r")
80-
file.close()
110+
local fileExists = file.exists(uri.file)
81111

82112
if not fileExists then
83113
-- gzip check
84-
fileExists = file.open(uri.file .. ".gz", "r")
85-
file.close()
114+
fileExists = file.exists(uri.file .. ".gz")
86115

87116
if fileExists then
88-
print("gzip variant exists, serving that one")
117+
-- print("gzip variant exists, serving that one")
89118
uri.file = uri.file .. ".gz"
90119
uri.isGzipped = true
91120
end
@@ -107,15 +136,20 @@ return function (conn)
107136
end
108137
end
109138
end
139+
--uart.write(0, "httpserver: onRequest(): about to start serving\n")
140+
110141
startServing(fileServeFunction, connection, req, uri.args)
142+
--uart.write(0, "httpserver: onRequest() end\n")
143+
111144
end
112145

113146
local tmp_payload = nil
114147
local bBodyMissing = nil
115148

149+
116150
local function onReceive(connection, payload)
117151
collectgarbage()
118-
152+
--uart.write(0, "httpserver: onReceive()\n")
119153
-- collect data packets until the size of http body meets the Content-Length stated in header
120154
if payload:find("Content%-Length:") or bBodyMissing then
121155
if tmp_payload then
@@ -127,33 +161,14 @@ return function (conn)
127161
bBodyMissing = true
128162
return
129163
else
130-
print("HTTP packet assembled! size: "..#tmp_payload)
164+
--print("HTTP packet assembled! size: "..#tmp_payload)
131165
payload = tmp_payload
132166
tmp_payload, bBodyMissing = nil
133167
end
134168
end
135169

136170

137-
local conf = {}
138-
if file.open("httpserver-conf-default.lc", "r") == nil then
139-
print("httpserver-conf-default.lc (default wifi config) not found, creating...")
140-
conf = dofile("httpserver-confmakedefault.lc")
141-
dofile("httpserver-confwrite.lc")(conf, "httpserver-conf-default.lua")
142-
dofile("compile.lc")("httpserver-conf-default.lua")
143-
end
144-
file.close()
145-
146-
--check if the user config file exists, if not save the default config to it
147-
if file.open("httpserver-conf.lc", "r") == nil then
148-
print("httpserver-conf.lc (user httpserver config) not found, creating from default...")
149-
conf = dofile("httpserver-confmakedefault.lc")
150-
dofile("httpserver-confwrite.lc")(conf, "httpserver-conf.lua")
151-
dofile("compile.lc")("httpserver-conf.lua")
152-
end
153-
file.close()
154-
155-
156-
local conf = dofile("httpserver-conf.lc")
171+
local conf = dofile("confload.lc")("httpserver-conf.lc")
157172
local auth
158173
local user = "Anonymous"
159174

@@ -181,30 +196,41 @@ return function (conn)
181196
end
182197
startServing(fileServeFunction, connection, req, args)
183198
end
199+
--uart.write(0, "httpserver: onReceive() end\n")
184200
end
185201

186202
local function onSent(connection, payload)
203+
--uart.write(0, "httpserver: onSent()\n")
187204
collectgarbage()
188205
if connectionThread then
189206
local connectionThreadStatus = coroutine.status(connectionThread)
190207
if connectionThreadStatus == "suspended" then
191208
-- Not finished sending file, resume.
209+
--uart.write(0, "httpserver: onSent(): about to resume coroutine\n")
192210
local status, err = coroutine.resume(connectionThread)
193211
if not status then
194-
print(err)
212+
print("Error: "..err)
195213
end
196214
elseif connectionThreadStatus == "dead" then
197215
-- We're done sending file.
198216
connection:close()
199217
connectionThread = nil
200218
end
201219
end
220+
--uart.write(0, "httpserver: onSent() end\n")
221+
202222
end
203223

204224
local function install(connection)
205225
connection:on("receive", onReceive)
206226
connection:on("sent", onSent)
227+
connection:on("disconnection",function(c)
228+
if connectionThread then
229+
connectionThread = nil
230+
collectgarbage()
231+
end
232+
end)
207233
end
208234

209-
return detect, install, onReceive
235+
return {detect = detect, install = install, onReceive = onReceive}
210236
end

init.lua

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ print('heap: ',node.heap())
1212

1313
--STEP2: compile all .lua files to .lc files
1414
local compilelua = "compile.lua"
15-
if file.open(compilelua) ~= nil then
16-
file.close()
15+
if file.exists(compilelua) then
1716
dofile(compilelua)(compilelua)
1817
end
1918
compilelua = nil
@@ -30,7 +29,7 @@ tools = nil
3029
dofile("wifi.lc")
3130

3231
--STEP6: start the TCP server in port 80, if an ip is available
33-
tcpsrv = dofile("tcpserver.lc")(80, {"httpserver", "luaserver"})
32+
tcpsrv = dofile("tcpserver.lc")(80, {["httpserver"] = true, ["luaserver"] = true})
3433

3534
--STEP7: start the tftp server for easy file upload
3635
tftpsrv = dofile("tftpd.lc")(69)

luaserver.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,5 @@ return function(connarg)
4646
print("Welcome to NodeMcu world.")
4747
end
4848

49-
return detect, install, onReceive
49+
return {detect = detect, install = install, onReceive = onReceive}
5050
end

0 commit comments

Comments
 (0)