Browse Source

Merge pull request #75 from marcoskirsch/master

Merge master to dev
Marcos 7 years ago
parent
commit
189e4a5c56
13 changed files with 191 additions and 160 deletions
  1. 7 5
      Makefile
  2. 3 2
      README.md
  3. 16 12
      http/cars.html
  4. 7 3
      http/file_list.lua
  5. 1 1
      http/index.html
  6. 16 6
      httpserver-b64decode.lua
  7. 14 3
      httpserver-basicauth.lua
  8. 7 5
      httpserver-conf.lua
  9. 59 67
      httpserver-connection.lua
  10. 4 3
      httpserver-header.lua
  11. 13 20
      httpserver-static.lua
  12. 0 5
      httpserver.lua
  13. 44 28
      init.lua

+ 7 - 5
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))
 

+ 3 - 2
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

+ 16 - 12
http/cars.html

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

+ 7 - 3
http/file_list.lua

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

+ 1 - 1
http/index.html

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

+ 16 - 6
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
-

+ 14 - 3
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()

+ 7 - 5
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

+ 59 - 67
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

+ 4 - 3
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

+ 13 - 20
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

+ 0 - 5
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

+ 44 - 28
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.
-
-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)
+-- 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
 
--- 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)
+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 (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