diff --git a/Makefile b/Makefile
index 3b07436..b9fcc3c 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,9 @@
NODEMCU-UPLOADER=../nodemcu-uploader/nodemcu-uploader.py
# Serial port
PORT=/dev/cu.SLAB_USBtoUART
-SPEED=9600
+SPEED=115200
+
+NODEMCU-COMMAND=$(NODEMCU-UPLOADER) -b $(SPEED) --start_baud $(SPEED) -p $(PORT) upload
######################################################################
# End of user config
@@ -33,17 +35,17 @@ usage:
# Upload one files only
upload:
- @python $(NODEMCU-UPLOADER) -b $(SPEED) -p $(PORT) upload $(FILE)
+ @python $(NODEMCU-COMMAND) $(FILE)
# Upload HTTP files only
upload_http: $(HTTP_FILES)
- @python $(NODEMCU-UPLOADER) -b $(SPEED) -p $(PORT) upload $(foreach f, $^, $(f))
+ @python $(NODEMCU-COMMAND) $(foreach f, $^, $(f))
# Upload httpserver lua files (init and server module)
upload_server: $(LUA_FILES)
- @python $(NODEMCU-UPLOADER) -b $(SPEED) -p $(PORT) upload $(foreach f, $^, $(f))
+ @python $(NODEMCU-COMMAND) $(foreach f, $^, $(f))
# Upload all
upload_all: $(LUA_FILES) $(HTTP_FILES)
- @python $(NODEMCU-UPLOADER) -b $(SPEED) -p $(PORT) upload $(foreach f, $^, $(f))
+ @python $(NODEMCU-COMMAND) $(foreach f, $^, $(f))
diff --git a/README.md b/README.md
index 5d01052..a7a0930 100644
--- a/README.md
+++ b/README.md
@@ -65,12 +65,13 @@ Let the abuse begin.
4. How to use HTTP Basic Authentication.
- Enable and configure HTTP Basic Authentication by editing "httpserver-conf.lua" file.
+ Modify variables in configuration file httpserver-conf.lua in order to enable and to configure usernames/passwords.
+ See comments in that file for more details.
When enabled, HTTP Basic Authentication is global to every file served by the server.
Remember that HTTP Basic Authentication is a very basic authentication protocol, and should not be
- considered secure if the server is not using encryption, as your username and password travel
+ considered as secure since the server is not using encryption. Username and passwords travel
in plain text.
## How to use server-side scripting using your own Lua scripts
diff --git a/http/cars.html b/http/cars.html
index 6467a8b..41793aa 100644
--- a/http/cars.html
+++ b/http/cars.html
@@ -1,17 +1,21 @@
-
-
- Nice cars
-
-
-
Nice cars!
-
This page loads "large" images of fancy cars. It is meant to serve as a stress test for nodemcu-httpserver.
+ This page loads "large" images of fancy cars. It is meant to serve as a stress test for nodemcu-httpserver.
+ It works with three embedded images of cars, but the server crashes with four. Edit this file and try it yourself.
+ Whoever manages to modify nodemcu-httpserver to load all four images without crashing wins a prize!
+
+ Ferrari
+ Lamborghini
+ Maserati
+ Porsche
+
diff --git a/http/file_list.lua b/http/file_list.lua
index 2935ec8..d994043 100644
--- a/http/file_list.lua
+++ b/http/file_list.lua
@@ -7,12 +7,16 @@ return function (connection, req, args)
Server File Listing
]===])
- local remaining, used, total=file.fsinfo()
+ local remaining, used, total = file.fsinfo()
connection:send("Total size: " .. total .. " bytes \n" ..
"In Use: " .. used .. " bytes \n" ..
- "Free: " .. remaining .. " bytes \n" ..
- "
\n")
for name, size in pairs(file.list()) do
local isHttpFile = string.match(name, "(http/)") ~= nil
if isHttpFile then
diff --git a/http/index.html b/http/index.html
index 94a8b51..8e033d6 100644
--- a/http/index.html
+++ b/http/index.html
@@ -20,7 +20,7 @@
Nice cars: Stress test, loads 4 "large" images. Makes the chip panic and restart :( (static)
+
Nice cars: Stress test, loads several "large" images. Makes the chip panic and restart :( (static)
Zipped: File is actually saved as zipped.html.gz. A compressed file! (static but gzipped)
Zipped: Same exact file as served above. Server is smart enough to treat the .gz extension correctly (static but gzipped)
Arguments: Parses arguments passed in the URL and prints them. (Lua)
diff --git a/httpserver-b64decode.lua b/httpserver-b64decode.lua
index e18bea6..a79581e 100755
--- a/httpserver-b64decode.lua
+++ b/httpserver-b64decode.lua
@@ -1,33 +1,44 @@
#!/usr/local/bin/lua
-- httpserver-b64decode.lua
-- Part of nodemcu-httpserver, contains b64 decoding used for HTTP Basic Authentication.
+-- Modified to use an exponentiation by multiplication method for only applicable for unsigned integers.
-- Based on http://lua-users.org/wiki/BaseSixtyFour by Alex Kloss
-- compatible with lua 5.1
-- http://www.it-rfc.de
-- Author: Marcos Kirsch
+local function uipow(a, b)
+ local ret = 1
+ if b >= 0 then
+ for i = 1, b do
+ ret = ret * a
+ end
+ end
+ return ret
+end
+
-- bitshift functions (<<, >> equivalent)
-- shift left
local function lsh(value,shift)
- return (value*(2^shift)) % 256
+ return (value*(uipow(2, shift))) % 256
end
-- shift right
local function rsh(value,shift)
-- Lua builds with no floating point don't define math.
- if math then return math.floor(value/2^shift) % 256 end
- return (value/2^shift) % 256
+ if math then return math.floor(value/uipow(2, shift)) % 256 end
+ return (value/uipow(2, shift)) % 256
end
-- return single bit (for OR)
local function bit(x,b)
- return (x % 2^b - x % 2^(b-1) > 0)
+ return (x % uipow(2, b) - x % uipow(2, (b-1)) > 0)
end
-- logic OR for number values
local function lor(x,y)
result = 0
- for p=1,8 do result = result + (((bit(x,p) or bit(y,p)) == true) and 2^(p-1) or 0) end
+ for p=1,8 do result = result + (((bit(x,p) or bit(y,p)) == true) and uipow(2, (p-1)) or 0) end
return result
end
@@ -62,4 +73,3 @@ return function(data)
end
return result
end
-
diff --git a/httpserver-basicauth.lua b/httpserver-basicauth.lua
index 09d4f78..42109ec 100644
--- a/httpserver-basicauth.lua
+++ b/httpserver-basicauth.lua
@@ -4,6 +4,16 @@
basicAuth = {}
+-- Returns true if the user/password match one of the users/passwords in httpserver-conf.lua.
+-- Returns false otherwise.
+function loginIsValid(user, pwd, users)
+ if user == nil then return false end
+ if pwd == nil then return false end
+ if users[user] == nil then return false end
+ if users[user] ~= pwd then return false end
+ return true
+end
+
-- Parse basic auth http header.
-- Returns the username if header contains valid credentials,
-- nil otherwise.
@@ -15,12 +25,13 @@ function basicAuth.authenticate(header)
end
local credentials = dofile("httpserver-b64decode.lc")(credentials_enc)
local user, pwd = credentials:match("^(.*):(.*)$")
- if user ~= conf.auth.user or pwd ~= conf.auth.password then
+ if loginIsValid(user, pwd, conf.auth.users) then
+ print("httpserver-basicauth: User \"" .. user .. "\": Authenticated.")
+ return user
+ else
print("httpserver-basicauth: User \"" .. user .. "\": Access denied.")
return nil
end
- print("httpserver-basicauth: User \"" .. user .. "\": Authenticated.")
- return user
end
function basicAuth.authErrorHeader()
diff --git a/httpserver-conf.lua b/httpserver-conf.lua
index 854e557..85ab358 100644
--- a/httpserver-conf.lua
+++ b/httpserver-conf.lua
@@ -4,12 +4,14 @@
local conf = {}
--- Basic Authentication Conf
+-- Configure Basic HTTP Authentication.
local auth = {}
+-- Set to true if you want to enable.
auth.enabled = false
-auth.realm = "nodemcu-httpserver" -- displayed in the login dialog users get
-auth.user = "user"
-auth.password = "password" -- PLEASE change this
-conf.auth = auth
+-- Displayed in the login dialog users see before authenticating.
+auth.realm = "nodemcu-httpserver"
+-- Add users and passwords to this table. Do not leave this unchanged if you enable authentication!
+auth.users = {user1 = "password1", user2 = "password2", user3 = "password3"}
+conf.auth = auth
return conf
diff --git a/httpserver-connection.lua b/httpserver-connection.lua
index 329e603..75d7c75 100644
--- a/httpserver-connection.lua
+++ b/httpserver-connection.lua
@@ -1,67 +1,59 @@
--- httpserver-connection
--- Part of nodemcu-httpserver, provides a buffered connection object that can handle multiple
--- consecutive send() calls, and buffers small payloads to send once they get big.
--- For this to work, it must be used from a coroutine and owner is responsible for the final
--- flush() and for closing the connection.
--- Author: Philip Gladstone, Marcos Kirsch
-
-BufferedConnection = {}
-
--- parameter is the nodemcu-firmware connection
-function BufferedConnection:new(connection)
- local newInstance = {}
- newInstance.connection = connection
- newInstance.size = 0
- newInstance.data = {}
-
- function newInstance:flush()
- if self.size > 0 then
- self.connection:send(table.concat(self.data, ""))
- self.data = {}
- self.size = 0
- return true
- end
- return false
- end
-
- function newInstance:send(payload)
- local flushthreshold = 1400
-
- local newsize = self.size + payload:len()
- while newsize > flushthreshold do
- --STEP1: cut out piece from payload to complete threshold bytes in table
- local piecesize = flushthreshold - self.size
- local piece = payload:sub(1, piecesize)
- payload = payload:sub(piecesize + 1, -1)
- --STEP2: insert piece into table
- table.insert(self.data, piece)
- self.size = self.size + piecesize --size should be same as flushthreshold
- --STEP3: flush entire table
- if self:flush() then
- coroutine.yield()
- end
- --at this point, size should be 0, because the table was just flushed
- newsize = self.size + payload:len()
- end
-
- --at this point, whatever is left in payload should be <= flushthreshold
- local plen = payload:len()
- if plen == flushthreshold then
- --case 1: what is left in payload is exactly flushthreshold bytes (boundary case), so flush it
- table.insert(self.data, payload)
- self.size = self.size + plen
- if self:flush() then
- coroutine.yield()
- end
- elseif payload:len() then
- --case 2: what is left in payload is less than flushthreshold, so just leave it in the table
- table.insert(self.data, payload)
- self.size = self.size + plen
- --else, case 3: nothing left in payload, so do nothing
- end
- end
-
- return newInstance
-end
-
-return BufferedConnection
+-- httpserver-connection
+-- Part of nodemcu-httpserver, provides a buffered connection object that can handle multiple
+-- consecutive send() calls, and buffers small payloads to send once they get big.
+-- For this to work, it must be used from a coroutine and owner is responsible for the final
+-- flush() and for closing the connection.
+-- Author: Philip Gladstone, Marcos Kirsch
+
+BufferedConnection = {}
+
+-- parameter is the nodemcu-firmware connection
+function BufferedConnection:new(connection)
+ local newInstance = {}
+ newInstance.connection = connection
+ newInstance.size = 0
+ newInstance.data = {}
+
+ -- Returns true if there was any data to be sent.
+ function newInstance:flush()
+ if self.size > 0 then
+ self.connection:send(table.concat(self.data, ""))
+ self.data = {}
+ self.size = 0
+ return true
+ end
+ return false
+ end
+
+ function newInstance:send(payload)
+ local flushThreshold = 1400
+ local newSize = self.size + payload:len()
+ while newSize >= flushThreshold do
+ --STEP1: cut out piece from payload to complete threshold bytes in table
+ local pieceSize = flushThreshold - self.size
+ local piece = payload:sub(1, pieceSize)
+ payload = payload:sub(pieceSize + 1, -1)
+ --STEP2: insert piece into table
+ table.insert(self.data, piece)
+ piece = nil
+ self.size = self.size + pieceSize --size should be same as flushThreshold
+ --STEP3: flush entire table
+ if self:flush() then
+ coroutine.yield()
+ end
+ --at this point, size should be 0, because the table was just flushed
+ newSize = self.size + payload:len()
+ end
+
+ --at this point, whatever is left in payload should be < flushThreshold
+ if payload:len() ~= 0 then
+ --leave remaining data in the table
+ table.insert(self.data, payload)
+ self.size = self.size + payload:len()
+ end
+ end
+ return newInstance
+
+end
+
+return BufferedConnection
diff --git a/httpserver-header.lua b/httpserver-header.lua
index 9c52b8c..3b69cd3 100644
--- a/httpserver-header.lua
+++ b/httpserver-header.lua
@@ -13,15 +13,16 @@ return function(connection, code, extension, isGzipped)
local function getMimeType(ext)
-- A few MIME types. Keep list short. If you need something that is missing, let's add it.
- 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" }
+ 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"}
if mt[ext] then return mt[ext] else return "text/plain" end
end
local mimeType = getMimeType(extension)
- connection:send("HTTP/1.0 " .. code .. " " .. getHTTPStatusString(code) .. "\r\nServer: nodemcu-httpserver\r\nContent-Type: " .. mimeType .. "\r\nCache-Control: private, no-store\r\n")
+ connection:send("HTTP/1.0 " .. code .. " " .. getHTTPStatusString(code) .. "\r\nServer: nodemcu-httpserver\r\nContent-Type: " .. mimeType .. "\r\n")
if isGzipped then
- connection:send("Cache-Control: max-age=2592000\r\nContent-Encoding: gzip\r\n")
+ connection:send("Cache-Control: private, max-age=2592000\r\nContent-Encoding: gzip\r\n")
end
connection:send("Connection: close\r\n\r\n")
end
diff --git a/httpserver-static.lua b/httpserver-static.lua
index 178b17b..68ba94c 100644
--- a/httpserver-static.lua
+++ b/httpserver-static.lua
@@ -3,32 +3,25 @@
-- Author: Marcos Kirsch
return function (connection, req, args)
- --print("Begin sending:", args.file)
- --print("node.heap(): ", node.heap())
dofile("httpserver-header.lc")(connection, 200, args.ext, args.isGzipped)
-- Send file in little chunks
- local continue = true
- local size = file.list()[args.file]
- local bytesSent = 0
+ local bytesRemaining = file.list()[args.file]
-- Chunks larger than 1024 don't work.
-- https://github.com/nodemcu/nodemcu-firmware/issues/1075
local chunkSize = 1024
- while continue do
- collectgarbage()
-
- -- NodeMCU file API lets you open 1 file at a time.
- -- So we need to open, seek, close each time in order
- -- to support multiple simultaneous clients.
- file.open(args.file)
- file.seek("set", bytesSent)
- local chunk = file.read(chunkSize)
- file.close()
-
+ local fileHandle = file.open(args.file)
+ while bytesRemaining > 0 do
+ local bytesToRead = 0
+ if bytesRemaining > chunkSize then bytesToRead = chunkSize else bytesToRead = bytesRemaining end
+ local chunk = fileHandle:read(bytesToRead)
connection:send(chunk)
- bytesSent = bytesSent + #chunk
+ bytesRemaining = bytesRemaining - #chunk
+ --print(args.file .. ": Sent "..#chunk.. " bytes, " .. bytesRemaining .. " to go.")
chunk = nil
- --print("Sent: " .. bytesSent .. " of " .. size)
- if bytesSent == size then continue = false end
+ collectgarbage()
end
- --print("Finished sending: ", args.file)
+ print("Finished sending: ", args.file)
+ fileHandle:close()
+ fileHandle = nil
+ collectgarbage()
end
diff --git a/httpserver.lua b/httpserver.lua
index 29ac0a2..5b29773 100644
--- a/httpserver.lua
+++ b/httpserver.lua
@@ -155,11 +155,6 @@ return function (port)
end
)
- -- false and nil evaluate as false
- local ip = wifi.sta.getip()
- if not ip then ip = wifi.ap.getip() end
- if not ip then ip = "unknown IP" end
- print("nodemcu-httpserver running at http://" .. ip .. ":" .. port)
return s
end
diff --git a/init.lua b/init.lua
index fe654a8..17a3748 100644
--- a/init.lua
+++ b/init.lua
@@ -5,7 +5,7 @@ local wifiConfig = {}
-- wifi.STATION -- station: join a WiFi network
-- wifi.SOFTAP -- access point: create a WiFi network
-- wifi.wifi.STATIONAP -- both station and access point
-wifiConfig.mode = wifi.STATIONAP -- both station and access point
+wifiConfig.mode = wifi.STATION
wifiConfig.accessPointConfig = {}
wifiConfig.accessPointConfig.ssid = "ESP-"..node.chipid() -- Name of the SSID you want to create
@@ -23,7 +23,7 @@ wifiConfig.stationPointConfig.pwd = "" -- Password for the WiFi
-- Tell the chip to connect to the access point
wifi.setmode(wifiConfig.mode)
-print('set (mode='..wifi.getmode()..')')
+--print('set (mode='..wifi.getmode()..')')
if (wifiConfig.mode == wifi.SOFTAP) or (wifiConfig.mode == wifi.STATIONAP) then
print('AP MAC: ',wifi.ap.getmac())
@@ -44,7 +44,7 @@ collectgarbage()
-- End WiFi configuration
-- Compile server code and remove original .lua files.
--- This only happens the first time afer the .lua files are uploaded.
+-- This only happens the first time after server .lua files are uploaded.
local compileAndRemoveIfNeeded = function(f)
if file.open(f) then
@@ -74,32 +74,48 @@ serverFiles = nil
collectgarbage()
-- Connect to the WiFi access point.
--- Once the device is connected, you may start the HTTP server.
+-- Once the device is connected, start the HTTP server.
+
+startServer = function(ip, hostname)
+ local serverPort = 80
+ if (dofile("httpserver.lc")(serverPort)) then
+ print("nodemcu-httpserver running at:")
+ print(" http://" .. ip .. ":" .. serverPort)
+ if (mdns) then
+ mdns.register(hostname, { description="A tiny server", service="http", port=serverPort, location='Earth' })
+ print (' http://' .. hostname .. '.local.:' .. serverPort)
+ end
+ end
+end
if (wifi.getmode() == wifi.STATION) or (wifi.getmode() == wifi.STATIONAP) then
- local joinCounter = 0
- local joinMaxAttempts = 5
- tmr.alarm(0, 3000, 1, function()
- local ip = wifi.sta.getip()
- if ip == nil and joinCounter < joinMaxAttempts then
- print('Connecting to WiFi Access Point ...')
- joinCounter = joinCounter +1
- else
- if joinCounter == joinMaxAttempts then
- print('Failed to connect to WiFi Access Point.')
- else
- print('IP: ',ip)
- end
- tmr.stop(0)
- joinCounter = nil
- joinMaxAttempts = nil
- collectgarbage()
- end
- end)
-end
-
--- Uncomment to automatically start the server in port 80
-if (not not wifi.sta.getip()) or (not not wifi.ap.getip()) then
- --dofile("httpserver.lc")(80)
+ local joinCounter = 0
+ local joinMaxAttempts = 5
+ tmr.alarm(0, 3000, 1, function()
+ local ip = wifi.sta.getip()
+ if (not ip) then ip = wifi.ap.getip() end
+ if ip == nil and joinCounter < joinMaxAttempts then
+ print('Connecting to WiFi Access Point ...')
+ joinCounter = joinCounter + 1
+ else
+ if joinCounter == joinMaxAttempts then
+ print('Failed to connect to WiFi Access Point.')
+ else
+ print("IP = " .. ip)
+ if (not not wifi.sta.getip()) or (not not wifi.ap.getip()) then
+ -- Second parameter is for mDNS (aka Zeroconf aka Bonjour) registration.
+ -- If the mdns module is compiled in the firmware, advertise the server with this name.
+ -- If no mdns is compiled, then parameter is ignored.
+ startServer(ip, "nodemcu")
+ end
+ end
+ tmr.stop(0)
+ joinCounter = nil
+ joinMaxAttempts = nil
+ collectgarbage()
+ end
+ end)
+else
+ startServer()
end