Merge pull request #75 from marcoskirsch/master

Merge master to dev
This commit is contained in:
Marcos 2017-01-27 21:59:05 -06:00 committed by GitHub
commit 189e4a5c56
13 changed files with 191 additions and 160 deletions

View File

@ -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))

View File

@ -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

View File

@ -1,17 +1,21 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Nice cars</title>
</head>
<body>
<h1>Nice cars!</h1>
<p>This page loads "large" images of fancy cars. It is meant to serve as a stress test for nodemcu-httpserver.</p>
<figure><img src="cars-ferrari.jpg" /><figcaption>Ferrari</figcaption></figure>
<figure><img src="cars-lambo.jpg" /><figcaption>Lamborghini</figcaption></figure>
<figure><img src="cars-mas.jpg" /><figcaption>Maserati</figcaption></figure>
<figure><img src="cars-porsche.jpg" /><figcaption>Porsche</figcaption></figure>
</body>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Nice cars</title>
</head>
<body>
<h1>Nice cars!</h1>
<p>
This page loads "large" images of fancy cars. It is meant to serve as a stress test for nodemcu-httpserver.<br>
It works with three embedded images of cars, but the server crashes with four. Edit this file and try it yourself.<br>
Whoever manages to modify nodemcu-httpserver to load all four images without crashing wins a prize!
</p>
<figure><img src="cars-ferrari.jpg" /><figcaption>Ferrari</figcaption></figure>
<figure><img src="cars-lambo.jpg" /><figcaption>Lamborghini</figcaption></figure>
<figure><img src="cars-mas.jpg" /><figcaption>Maserati</figcaption></figure>
<figure><img src="cars-porsche.jpg" /><figcaption>Porsche</figcaption></figure>
</body>
</html>

View File

@ -7,12 +7,16 @@ return function (connection, req, args)
<h1>Server File Listing</h1>
]===])
local remaining, used, total=file.fsinfo()
local remaining, used, total = file.fsinfo()
connection:send("<b>Total size: </b> " .. total .. " bytes<br/>\n" ..
"<b>In Use: </b> " .. used .. " bytes<br/>\n" ..
"<b>Free: </b> " .. remaining .. " bytes<br/>\n" ..
"<p>\n<b>Files:</b><br/>\n<ul>\n")
"<b>Free: </b> " .. remaining .. " bytes<br/>\n")
local flashAddress, flashSize = file.fscfg ()
connection:send("<b>Flash Address: </b> " .. flashAddress .. " bytes<br/>\n" ..
"<b>Flash Size: </b> " .. flashSize .. " bytes<br/>\n")
connection:send("<p>\n<b>Files:</b><br/>\n<ul>\n")
for name, size in pairs(file.list()) do
local isHttpFile = string.match(name, "(http/)") ~= nil
if isHttpFile then

View File

@ -20,7 +20,7 @@
<h3>Serve me some pages!</h3>
<ul>
<li><a href="index.html">Index</a>: This page (static)</li>
<li><a href="cars.html">Nice cars</a>: Stress test, loads 4 "large" images. Makes the chip panic and restart :( (static)</li>
<li><a href="cars.html">Nice cars</a>: Stress test, loads several "large" images. Makes the chip panic and restart :( (static)</li>
<li><a href="zipped.html">Zipped</a>: File is actually saved as zipped.html.gz. A compressed file! (static but gzipped)</li>
<li><a href="zipped.html.gz">Zipped</a>: Same exact file as served above. Server is smart enough to treat the .gz extension correctly (static but gzipped)</li>
<li><a href="args.lua">Arguments</a>: Parses arguments passed in the URL and prints them. (Lua)</li>

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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