From bbcce2d64dac0580d31255d313c0e60c13fc9d48 Mon Sep 17 00:00:00 2001 From: Pospa Date: Sun, 9 Aug 2015 15:18:08 +0200 Subject: [PATCH 01/15] AP mode support added --- init.lua | 174 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 96 insertions(+), 78 deletions(-) diff --git a/init.lua b/init.lua index 45aaeab..9456096 100644 --- a/init.lua +++ b/init.lua @@ -1,78 +1,96 @@ --- Begin WiFi configuration - -local wifiConfig = {} - --- wifi.STATION -- station: join a WiFi network --- wifi.AP -- access point: create a WiFi network --- wifi.wifi.STATIONAP -- both station and access point -wifiConfig.mode = wifi.STATIONAP -- both station and access point - -wifiConfig.accessPointConfig = {} -wifiConfig.accessPointConfig.ssid = "ESP-"..node.chipid() -- Name of the SSID you want to create -wifiConfig.accessPointConfig.pwd = "ESP-"..node.chipid() -- WiFi password - at least 8 characters - -wifiConfig.stationPointConfig = {} -wifiConfig.stationPointConfig.ssid = "Internet" -- Name of the WiFi network you want to join -wifiConfig.stationPointConfig.pwd = "" -- Password for the WiFi network - --- Tell the chip to connect to the access point - -wifi.setmode(wifiConfig.mode) -print('set (mode='..wifi.getmode()..')') -print('MAC: ',wifi.sta.getmac()) -print('chip: ',node.chipid()) -print('heap: ',node.heap()) - -wifi.ap.config(wifiConfig.accessPointConfig) -wifi.sta.config(wifiConfig.stationPointConfig.ssid, wifiConfig.stationPointConfig.pwd) -wifiConfig = nil -collectgarbage() - --- End WiFi configuration - --- Compile server code and remove original .lua files. --- This only happens the first time afer the .lua files are uploaded. - -local compileAndRemoveIfNeeded = function(f) - if file.open(f) then - file.close() - print('Compiling:', f) - node.compile(f) - file.remove(f) - collectgarbage() - end -end - -local serverFiles = {'httpserver.lua', 'httpserver-basicauth.lua', 'httpserver-conf.lua', 'httpserver-b64decode.lua', 'httpserver-request.lua', 'httpserver-static.lua', 'httpserver-header.lua', 'httpserver-error.lua'} -for i, f in ipairs(serverFiles) do compileAndRemoveIfNeeded(f) end - -compileAndRemoveIfNeeded = nil -serverFiles = nil -collectgarbage() - --- Connect to the WiFi access point. --- Once the device is connected, you may start the HTTP server. - -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) - -- Uncomment to automatically start the server in port 80 - --dofile("httpserver.lc")(80) - end - tmr.stop(0) - joinCounter = nil - joinMaxAttempts = nil - collectgarbage() - end - -end) - +-- Begin WiFi configuration + +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.accessPointConfig = {} +wifiConfig.accessPointConfig.ssid = "ESP-"..node.chipid() -- Name of the SSID you want to create +wifiConfig.accessPointConfig.pwd = "ESP-"..node.chipid() -- WiFi password - at least 8 characters + +wifiConfig.accessPointIpConfig = {} +wifiConfig.accessPointIpConfig.ip = "192.168.111.1" +wifiConfig.accessPointIpConfig.netmask = "255.255.255.0" +wifiConfig.accessPointIpConfig.gateway = "192.168.111.1" + +wifiConfig.stationPointConfig = {} +wifiConfig.stationPointConfig.ssid = "Internet" -- Name of the WiFi network you want to join +wifiConfig.stationPointConfig.pwd = "" -- Password for the WiFi network + +-- Tell the chip to connect to the access point + +wifi.setmode(wifiConfig.mode) +print('set (mode='..wifi.getmode()..')') + +if (wifiConfig.mode == wifi.SOFTAP) or (wifiConfig.mode == wifi.STATIONAP) then + print('AP MAC: ',wifi.ap.getmac()) + wifi.ap.config(wifiConfig.accessPointConfig) + wifi.ap.setip(wifiConfig.accessPointIpConfig) +end +if (wifiConfig.mode == wifi.STATION) or (wifiConfig.mode == wifi.STATIONAP) then + print('Client MAC: ',wifi.sta.getmac()) + wifi.sta.config(wifiConfig.stationPointConfig.ssid, wifiConfig.stationPointConfig.pwd, 1) +end + +print('chip: ',node.chipid()) +print('heap: ',node.heap()) + +wifiConfig = nil +collectgarbage() + +-- End WiFi configuration + +-- Compile server code and remove original .lua files. +-- This only happens the first time afer the .lua files are uploaded. + +local compileAndRemoveIfNeeded = function(f) + if file.open(f) then + file.close() + print('Compiling:', f) + node.compile(f) + file.remove(f) + collectgarbage() + end +end + +local serverFiles = {'httpserver.lua', 'httpserver-basicauth.lua', 'httpserver-conf.lua', 'httpserver-b64decode.lua', 'httpserver-request.lua', 'httpserver-static.lua', 'httpserver-header.lua', 'httpserver-error.lua'} +for i, f in ipairs(serverFiles) do compileAndRemoveIfNeeded(f) end + +compileAndRemoveIfNeeded = nil +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) +end + +-- Uncomment to automatically start the server in port 80 +if (not not wifi.sta.getip()) or (not not wifi.ap.getip()l) then + dofile("httpserver.lc")(80) +end + + From 13e2bfbe9c4f9ac8583ef49da6bdcf6fe86aa755 Mon Sep 17 00:00:00 2001 From: Hazar Karabay Date: Sat, 29 Aug 2015 15:30:37 +0300 Subject: [PATCH 02/15] Use gzipped file if exists If foo.html is requested and not found, foo.html.gz is checked and if exists, will served. --- httpserver.lua | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/httpserver.lua b/httpserver.lua index 8435178..b48ef2a 100644 --- a/httpserver.lua +++ b/httpserver.lua @@ -23,8 +23,21 @@ return function (port) fileServeFunction = dofile("httpserver-error.lc") else local fileExists = file.open(uri.file, "r") + file.close() + + if not fileExists then + -- gzip check + fileExists = file.open(uri.file .. ".gz", "r") file.close() - if not fileExists then + + if fileExists then + print("gzip variant exists, serving that one") + uri.file = uri.file .. ".gz" + uri.ext = uri.ext .. ".gz" + end + end + + if not fileExists then uri.args = {code = 404, errorString = "Not Found"} fileServeFunction = dofile("httpserver-error.lc") elseif uri.isScript then From ef340bc82a39bbd826a330d06d25f55cd98c34e6 Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Mon, 31 Aug 2015 18:01:06 -0700 Subject: [PATCH 03/15] Implement support for more arbitrary HTTP methods. Allows GET PUT and POST. With minor changes needed to support others --- README.md | 7 ++--- http/args.lua | 2 +- http/file_list.lua | 2 +- http/garage_door_opener.lua | 2 +- http/index.html | 2 +- http/node_info.lua | 2 +- http/post.html | 7 ----- http/post.lua | 33 +++++++++++++++++++++++ httpserver-error.lua | 2 +- httpserver-request.lua | 53 ++++++++++++++++++++++++++++++++++++- httpserver-static.lua | 2 +- httpserver.lua | 51 ++++++++++++++++++++++------------- 12 files changed, 128 insertions(+), 37 deletions(-) delete mode 100644 http/post.html create mode 100644 http/post.lua diff --git a/README.md b/README.md index db019c9..aadf9c5 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,14 @@ A (very) simple web server written in Lua for the ESP8266 running the NodeMCU fi ## Features -* GET +* GET, POST, PUT and minor changes to support other methods * Multiple MIME types * Error pages (404 and others) * Server-side execution of Lua scripts -* Query string argument parsing +* Query string argument parsing with decoding of arguments * Serving .gz compressed files * HTTP Basic Authentication +* Decoding of request bodies in both application/x-www-form-urlencoded and application/json (if cjson is available) ## How to use @@ -130,7 +131,7 @@ A (very) simple web server written in Lua for the ESP8266 running the NodeMCU fi ## Not supported -* Other methods: HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH +* ~~Other methods: HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH~~ * Encryption * Multiple users (HTTP Basic Authentication) * Only protect certain directories (HTTP Basic Authentication) diff --git a/http/args.lua b/http/args.lua index c9310c4..f7a2790 100644 --- a/http/args.lua +++ b/http/args.lua @@ -1,4 +1,4 @@ -return function (connection, args) +return function (connection, req, args) connection:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nCache-Control: private, no-store\r\n\r\n") connection:send('Arguments') connection:send('') diff --git a/http/file_list.lua b/http/file_list.lua index 384d088..9e92a23 100644 --- a/http/file_list.lua +++ b/http/file_list.lua @@ -1,4 +1,4 @@ -return function (connection, args) +return function (connection, req, args) connection:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nCache-Control: private, no-store\r\n\r\n") connection:send('Server File Listing') connection:send('') diff --git a/http/garage_door_opener.lua b/http/garage_door_opener.lua index e5dc874..ca67617 100644 --- a/http/garage_door_opener.lua +++ b/http/garage_door_opener.lua @@ -21,7 +21,7 @@ local function pushTheButton(connection, pin) end -return function (connection, args) +return function (connection, req, args) print('Garage door button was pressed!', args.door) if args.door == "1" then pushTheButton(connection, 1) -- GPIO1 elseif args.door == "2" then pushTheButton(connection, 2) -- GPIO2 diff --git a/http/index.html b/http/index.html index ef81901..0db6ace 100644 --- a/http/index.html +++ b/http/index.html @@ -22,7 +22,7 @@
  • Index: This page (static)
  • Zipped: A compressed file (static)
  • Arguments: Parses arguments passed in the URL and prints them. (Lua)
  • -
  • Post: A form that uses POST method, should error. (static)
  • +
  • Post: A form that uses POST method. Displays different content based on HTTP method. (Lua)
  • Garage door opener: Control GPIO lines via the server. (Lua)
  • NodeMCU info: Shows some basic NodeMCU(Lua)
  • List all server files: Displays a list of all the server files. (Lua)
  • diff --git a/http/node_info.lua b/http/node_info.lua index fdd8868..86e55eb 100644 --- a/http/node_info.lua +++ b/http/node_info.lua @@ -7,7 +7,7 @@ local function sendAttr(connection, attr, val) connection:send("
  • ".. attr .. ": " .. val .. "
  • \n") end -return function (connection, args) +return function (connection, req, args) collectgarbage() sendHeader(connection) connection:send('A Lua script sample

    Node info

      ') diff --git a/http/post.html b/http/post.html deleted file mode 100644 index 508c755..0000000 --- a/http/post.html +++ /dev/null @@ -1,7 +0,0 @@ - -Post -

      Post

      -This form uses POST method which is not supported by nodemcu-httpserver.
      -You should get an error when you press submit. -
      - diff --git a/http/post.lua b/http/post.lua new file mode 100644 index 0000000..d1ac03d --- /dev/null +++ b/http/post.lua @@ -0,0 +1,33 @@ +return function (connection, req, args) + connection:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nCache-Control: private, no-store\r\n\r\n") + connection:send('Arguments') + connection:send('') + connection:send('

      Arguments

      ') + + local form = [===[ +
      + First name:

      + Last name:

      + MaleFemale
      + +
      + ]===] + + if req.method == "GET" then + connection:send(form) + elseif req.method == "POST" then + local rd = req.getRequestData() + -- connection:send(cjson.encode(rd)) + connection:send('

      Received the following values:

      ') + connection:send("
        \n") + for name, value in pairs(rd) do + connection:send('
      • ' .. name .. ': ' .. tostring(value) .. "
      • \n") + end + + connection:send("
      \n") + else + connection:send("NOT IMPLEMENTED") + end + + connection:send('') +end diff --git a/httpserver-error.lua b/httpserver-error.lua index 00e6bd8..b4dba56 100644 --- a/httpserver-error.lua +++ b/httpserver-error.lua @@ -2,7 +2,7 @@ -- Part of nodemcu-httpserver, handles sending error pages to client. -- Author: Marcos Kirsch -return function (connection, args) +return function (connection, req, args) local function sendHeader(connection, code, errorString, extraHeaders, mimeType) connection:send("HTTP/1.0 " .. code .. " " .. errorString .. "\r\nServer: nodemcu-httpserver\r\nContent-Type: " .. mimeType .. "\r\n") diff --git a/httpserver-request.lua b/httpserver-request.lua index 3384667..121dbf8 100644 --- a/httpserver-request.lua +++ b/httpserver-request.lua @@ -12,17 +12,67 @@ local function uriToFilename(uri) return "http/" .. string.sub(uri, 2, -1) end +local function hex_to_char(x) + return string.char(tonumber(x, 16)) +end + +local function uri_decode(input) + return input:gsub("%+", " "):gsub("%%(%x%x)", hex_to_char) +end + local function parseArgs(args) local r = {}; i=1 if args == nil or args == "" then return r end for arg in string.gmatch(args, "([^&]+)") do local name, value = string.match(arg, "(.*)=(.*)") - if name ~= nil then r[name] = value end + if name ~= nil then r[name] = uri_decode(value) end i = i + 1 end return r end +local function parseFormData(body) + local data = {} + print("Parsing Form Data") + for kv in body.gmatch(body, "%s*&?([^=]+=[^&]+)") do + local key, value = string.match(kv, "(.*)=(.*)") + + print("Parsed: " .. key .. " => " .. value) + data[key] = uri_decode(value) + end + + return data +end + +local function getRequestData(payload) + local requestData + return function () + print("Getting Request Data") + if requestData then + return requestData + else + local mimeType = string.match(payload, "Content%-Type: (%S+)\r\n") + local body_start = payload:find("\r\n\r\n", 1, true) + local body = payload:sub(body_start, #payload) + payload = nil + collectgarbage() + + -- print("mimeType = [" .. mimeType .. "]") + + if mimeType == "application/json" then + print("JSON: " .. body) + requestData = cjson.decode(body) + elseif mimeType == "application/x-www-form-urlencoded" then + requestData = parseFormData(body) + else + requestData = {} + end + + return requestData + end + end +end + local function parseUri(uri) local r = {} local filename @@ -60,5 +110,6 @@ return function (request) _, i, r.method, r.request = line:find("^([A-Z]+) (.-) HTTP/[1-9]+.[0-9]+$") r.methodIsValid = validateMethod(r.method) r.uri = parseUri(r.request) + r.getRequestData = getRequestData(request) return r end diff --git a/httpserver-static.lua b/httpserver-static.lua index d3ac1bd..93ecc2a 100644 --- a/httpserver-static.lua +++ b/httpserver-static.lua @@ -2,7 +2,7 @@ -- Part of nodemcu-httpserver, handles sending static files to client. -- Author: Marcos Kirsch -return function (connection, args) +return function (connection, req, args) dofile("httpserver-header.lc")(connection, 200, args.ext) --print("Begin sending:", args.file) -- Send file in little chunks diff --git a/httpserver.lua b/httpserver.lua index b48ef2a..2b0376a 100644 --- a/httpserver.lua +++ b/httpserver.lua @@ -13,42 +13,54 @@ return function (port) -- We do it in a separate thread because we need to yield when sending lots -- of data in order to avoid overflowing the mcu's buffer. local connectionThread + + local allowStatic = {GET=true, HEAD=true, POST=false, PUT=false, DELETE=false, TRACE=false, OPTIONS=false, CONNECT=false, PATCH=false} - local function onGet(connection, uri) + local function onRequest(connection, req) collectgarbage() + local method = req.method + local uri = req.uri local fileServeFunction = nil + + print("Method: " .. method); + if #(uri.file) > 32 then -- nodemcu-firmware cannot handle long filenames. uri.args = {code = 400, errorString = "Bad Request"} fileServeFunction = dofile("httpserver-error.lc") else local fileExists = file.open(uri.file, "r") - file.close() - - if not fileExists then - -- gzip check - fileExists = file.open(uri.file .. ".gz", "r") file.close() + + if not fileExists then + -- gzip check + fileExists = file.open(uri.file .. ".gz", "r") + file.close() - if fileExists then - print("gzip variant exists, serving that one") - uri.file = uri.file .. ".gz" - uri.ext = uri.ext .. ".gz" + if fileExists then + print("gzip variant exists, serving that one") + uri.file = uri.file .. ".gz" + uri.ext = uri.ext .. ".gz" + end end - end - - if not fileExists then + + if not fileExists then uri.args = {code = 404, errorString = "Not Found"} fileServeFunction = dofile("httpserver-error.lc") elseif uri.isScript then fileServeFunction = dofile(uri.file) else - uri.args = {file = uri.file, ext = uri.ext} - fileServeFunction = dofile("httpserver-static.lc") + if allowStatic[method] then + uri.args = {file = uri.file, ext = uri.ext} + fileServeFunction = dofile("httpserver-static.lc") + else + uri.args = {code = 405, errorString = "Method not supported"} + fileServeFunction = dofile("httpserver-error.lc") + end end end connectionThread = coroutine.create(fileServeFunction) - coroutine.resume(connectionThread, connection, uri.args) + coroutine.resume(connectionThread, connection, req, uri.args) end local function onReceive(connection, payload) @@ -64,8 +76,9 @@ return function (port) auth = dofile("httpserver-basicauth.lc") user = auth.authenticate(payload) -- authenticate returns nil on failed auth end - if user and req.methodIsValid and req.method == "GET" then - onGet(connection, req.uri) + + if user and req.methodIsValid and (req.method == "GET" or req.method == "POST" or req.method == "PUT") then + onRequest(connection, req) else local args = {} local fileServeFunction = dofile("httpserver-error.lc") @@ -77,7 +90,7 @@ return function (port) args = {code = 400, errorString = "Bad Request"} end connectionThread = coroutine.create(fileServeFunction) - coroutine.resume(connectionThread, connection, args) + coroutine.resume(connectionThread, connection, req, args) end end From b7f78481a2984eeb25d198750a9f01c9b5ab081c Mon Sep 17 00:00:00 2001 From: Hazar Karabay Date: Wed, 16 Sep 2015 22:10:21 +0300 Subject: [PATCH 04/15] File extension parsing fix If a filename contains dots, extension was parsed as everything after the first dot. File extension must be what it is after the last dot. Also includes a rewrited workaround for mimetypes if the requested file gzip compressed. --- httpserver-header.lua | 9 ++------- httpserver-request.lua | 7 ++++++- httpserver-static.lua | 2 +- httpserver.lua | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/httpserver-header.lua b/httpserver-header.lua index 5f01ef7..4284dcf 100644 --- a/httpserver-header.lua +++ b/httpserver-header.lua @@ -2,7 +2,7 @@ -- Part of nodemcu-httpserver, knows how to send an HTTP header. -- Author: Marcos Kirsch -return function (connection, code, extension) +return function (connection, code, extension, gzip) local function getHTTPStatusString(code) local codez = {[200]="OK", [400]="Bad Request", [404]="Not Found",} @@ -15,11 +15,6 @@ return function (connection, code, extension) local gzip = false -- 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"} - -- add comressed flag if file ends with gz - if ext:find("%.gz$") then - ext = ext:sub(1, -4) - gzip = true - end if mt[ext] then contentType = mt[ext] else contentType = "text/plain" end return {contentType = contentType, gzip = gzip} end @@ -27,7 +22,7 @@ return function (connection, code, extension) local mimeType = getMimeType(extension) connection:send("HTTP/1.0 " .. code .. " " .. getHTTPStatusString(code) .. "\r\nServer: nodemcu-httpserver\r\nContent-Type: " .. mimeType["contentType"] .. "\r\n") - if mimeType["gzip"] then + if gzip then connection:send("Content-Encoding: gzip\r\n") end connection:send("Connection: close\r\n\r\n") diff --git a/httpserver-request.lua b/httpserver-request.lua index 121dbf8..944617d 100644 --- a/httpserver-request.lua +++ b/httpserver-request.lua @@ -94,7 +94,12 @@ local function parseUri(uri) filename,ext = filename:match("(.+)%.(.+)") table.insert(fullExt,1,ext) end - r.ext = table.concat(fullExt,".") + if #fullExt > 1 and fullExt[#fullExt] == 'gz' then + r.ext = fullExt[#fullExt-1] + r.isGzipped = true + elseif #fullExt >= 1 then + r.ext = fullExt[#fullExt] + end r.isScript = r.ext == "lua" or r.ext == "lc" r.file = uriToFilename(r.file) return r diff --git a/httpserver-static.lua b/httpserver-static.lua index 93ecc2a..5a57bd5 100644 --- a/httpserver-static.lua +++ b/httpserver-static.lua @@ -3,7 +3,7 @@ -- Author: Marcos Kirsch return function (connection, req, args) - dofile("httpserver-header.lc")(connection, 200, args.ext) + dofile("httpserver-header.lc")(connection, 200, args.ext, args.gzipped) --print("Begin sending:", args.file) -- Send file in little chunks local continue = true diff --git a/httpserver.lua b/httpserver.lua index 2b0376a..1f8f818 100644 --- a/httpserver.lua +++ b/httpserver.lua @@ -40,7 +40,7 @@ return function (port) if fileExists then print("gzip variant exists, serving that one") uri.file = uri.file .. ".gz" - uri.ext = uri.ext .. ".gz" + uri.isGzipped = true end end @@ -51,7 +51,7 @@ return function (port) fileServeFunction = dofile(uri.file) else if allowStatic[method] then - uri.args = {file = uri.file, ext = uri.ext} + uri.args = {file = uri.file, ext = uri.ext, gzipped = uri.isGzipped} fileServeFunction = dofile("httpserver-static.lc") else uri.args = {code = 405, errorString = "Method not supported"} From 0ddaa52527b8497972515bd9141a972aad7dd13d Mon Sep 17 00:00:00 2001 From: Hazar Karabay Date: Wed, 16 Sep 2015 22:10:49 +0300 Subject: [PATCH 05/15] init.lua typo Deleting stray character at line 92. --- init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 9456096..8b54dd5 100644 --- a/init.lua +++ b/init.lua @@ -89,7 +89,7 @@ if (wifi.getmode() == wifi.STATION) or (wifi.getmode() == wifi.STATIONAP) then end -- Uncomment to automatically start the server in port 80 -if (not not wifi.sta.getip()) or (not not wifi.ap.getip()l) then +if (not not wifi.sta.getip()) or (not not wifi.ap.getip()) then dofile("httpserver.lc")(80) end From 72b21a61ae11f35475c8bad3510ce427e984a6c0 Mon Sep 17 00:00:00 2001 From: Lionello Lunesu Date: Sun, 4 Oct 2015 00:02:40 +0800 Subject: [PATCH 06/15] Fixed syntax error on line 92 --- init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 9456096..8b54dd5 100644 --- a/init.lua +++ b/init.lua @@ -89,7 +89,7 @@ if (wifi.getmode() == wifi.STATION) or (wifi.getmode() == wifi.STATIONAP) then end -- Uncomment to automatically start the server in port 80 -if (not not wifi.sta.getip()) or (not not wifi.ap.getip()l) then +if (not not wifi.sta.getip()) or (not not wifi.ap.getip()) then dofile("httpserver.lc")(80) end From b4a2d02431213d82da56c3c80351b0e96219f5a8 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sun, 22 Nov 2015 13:39:03 -0500 Subject: [PATCH 07/15] Fix the problem with the more modern SDK only allowing one outstanding connection:send at a time. Long and short of it, don't use coroutine.yield any more when serving content. --- README.md | 5 ----- http/file_list.lua | 3 --- httpserver-static.lua | 1 - httpserver.lua | 43 ++++++++++++++++++++++++++++++++++++------- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index aadf9c5..8162cf3 100644 --- a/README.md +++ b/README.md @@ -78,11 +78,6 @@ A (very) simple web server written in Lua for the ESP8266 running the NodeMCU fi For example, if the client requests _http://2.2.2.2/foo.lua?color=red_ then the server will execute the function in your Lua script _foo.lua_ and pass in _connection_ and _args_, where _args.color = "red"_. - If you are going to be sending lots (as in over a KB) of data in your script, you should yield the thread/coroutine - every now and then in order to avoid overflowing the send buffer in the microcontroller. Use: - - coroutine.yield() - Look at the included example scripts for more ideas. ### Example: Garage door opener diff --git a/http/file_list.lua b/http/file_list.lua index 9e92a23..6879900 100644 --- a/http/file_list.lua +++ b/http/file_list.lua @@ -2,7 +2,6 @@ return function (connection, req, args) connection:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nCache-Control: private, no-store\r\n\r\n") connection:send('Server File Listing') connection:send('') - coroutine.yield() connection:send('

      Server File Listing

      ') local remaining, used, total=file.fsinfo() @@ -20,8 +19,6 @@ return function (connection, req, args) if isHttpFile then local url = string.match(name, ".*/(.*)") connection:send('
    • ' .. url .. " (" .. size .. " bytes)
    • \n") - -- this list could be very long, so we'll yield in order to avoid overflowing the send buffer. - coroutine.yield() end end connection:send("
    \n") diff --git a/httpserver-static.lua b/httpserver-static.lua index 5a57bd5..d7cb86f 100644 --- a/httpserver-static.lua +++ b/httpserver-static.lua @@ -20,7 +20,6 @@ return function (connection, req, args) if chunk == nil then continue = false else - coroutine.yield() connection:send(chunk) bytesSent = bytesSent + #chunk chunk = nil diff --git a/httpserver.lua b/httpserver.lua index 1f8f818..e0970c6 100644 --- a/httpserver.lua +++ b/httpserver.lua @@ -13,9 +13,37 @@ return function (port) -- We do it in a separate thread because we need to yield when sending lots -- of data in order to avoid overflowing the mcu's buffer. local connectionThread - + local allowStatic = {GET=true, HEAD=true, POST=false, PUT=false, DELETE=false, TRACE=false, OPTIONS=false, CONNECT=false, PATCH=false} + local function startServing(fileServeFunction, connection, req, args) + local bufferedConnection = {} + connectionThread = coroutine.create(function(fileServeFunction, connection, req, args) + fileServeFunction(connection, req, args) + connection:flush() + end) + function bufferedConnection:flush() + connection:send(table.concat(self.data, "")) + self.data = {} + self.size = 0 + end + function bufferedConnection:send(payload) + local l = payload:len() + if l + self.size > 1400 then + self:flush() + coroutine.yield() + end + table.insert(self.data, payload) + self.size = self.size + l + end + bufferedConnection.size = 0 + bufferedConnection.data = {} + local status, err = coroutine.resume(connectionThread, fileServeFunction, bufferedConnection, req, args) + if not status then + print(err) + end + end + local function onRequest(connection, req) collectgarbage() local method = req.method @@ -59,8 +87,7 @@ return function (port) end end end - connectionThread = coroutine.create(fileServeFunction) - coroutine.resume(connectionThread, connection, req, uri.args) + startServing(fileServeFunction, connection, req, uri.args) end local function onReceive(connection, payload) @@ -89,18 +116,20 @@ return function (port) else args = {code = 400, errorString = "Bad Request"} end - connectionThread = coroutine.create(fileServeFunction) - coroutine.resume(connectionThread, connection, req, args) + startServing(fileServeFunction, connection, req, args) end end local function onSent(connection, payload) collectgarbage() if connectionThread then - local connectionThreadStatus = coroutine.status(connectionThread) + local connectionThreadStatus = coroutine.status(connectionThread) if connectionThreadStatus == "suspended" then -- Not finished sending file, resume. - coroutine.resume(connectionThread) + local status, err = coroutine.resume(connectionThread) + if not status then + print(err) + end elseif connectionThreadStatus == "dead" then -- We're done sending file. connection:close() From 8a195cb79833592546555a3013ddf81f832ac64d Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sat, 28 Nov 2015 12:22:20 -0500 Subject: [PATCH 08/15] Reduce memory requirements a bit and fix weird issue in node_info.lua example --- http/Thumbs.db | Bin 0 -> 8192 bytes http/node_info.lua | 6 ++++-- http/node_info.lua~ | 29 +++++++++++++++++++++++++++++ httpserver.lua | 31 ++++++++++++++++++++++--------- 4 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 http/Thumbs.db create mode 100644 http/node_info.lua~ diff --git a/http/Thumbs.db b/http/Thumbs.db new file mode 100644 index 0000000000000000000000000000000000000000..9707bc379f96ec6f642f124233766c24e741d296 GIT binary patch literal 8192 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".. attr .. ": " .. val .. "
    \n") + if val then + connection:send("
  • ".. attr .. ": " .. val .. "
  • \n") + end end return function (connection, req, args) collectgarbage() sendHeader(connection) connection:send('A Lua script sample

    Node info

      ') - majorVer, minorVer, devVer, chipid, flashid, flashsize, flashmode, flashspeed = node.info(); + local majorVer, minorVer, devVer, chipid, flashid, flashsize, flashmode, flashspeed = node.info(); sendAttr(connection, "NodeMCU version" , majorVer.."."..minorVer.."."..devVer) sendAttr(connection, "chipid" , chipid) sendAttr(connection, "flashid" , flashid) diff --git a/http/node_info.lua~ b/http/node_info.lua~ new file mode 100644 index 0000000..8f9d615 --- /dev/null +++ b/http/node_info.lua~ @@ -0,0 +1,29 @@ +local function sendHeader(connection) + connection:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nCache-Control: private, no-store\r\n\r\n") + +end + +local function sendAttr(connection, attr, val) + if val then + connection:send("
    • ".. attr .. ": " .. val .. "
    • \n") + end +end + +return function (connection, req, args) + collectgarbage() + sendHeader(connection) + connection:send('A Lua script sample

      Node info

        ') + local majorVer, minorVer, devVer, chipid, flashid, flashsize, flashmode, flashspeed = node.info(); + --local x = (majorVer..".")..(minorVer..".")..devVer + sendAttr(connection, "NodeMCU version" , majorVer.."."..minorVer.."."..devVer) + sendAttr(connection, "chipid" , chipid) + sendAttr(connection, "flashid" , flashid) + sendAttr(connection, "flashsize" , flashsize) + sendAttr(connection, "flashmode" , flashmode) + sendAttr(connection, "flashspeed" , flashspeed) + sendAttr(connection, "node.heap()" , node.heap()) + sendAttr(connection, 'Memory in use (KB)' , collectgarbage("count")) + sendAttr(connection, 'IP address' , wifi.sta.getip()) + sendAttr(connection, 'MAC address' , wifi.sta.getmac()) + connection:send('
      ') +end diff --git a/httpserver.lua b/httpserver.lua index e0970c6..6695196 100644 --- a/httpserver.lua +++ b/httpserver.lua @@ -20,21 +20,34 @@ return function (port) local bufferedConnection = {} connectionThread = coroutine.create(function(fileServeFunction, connection, req, args) fileServeFunction(connection, req, args) - connection:flush() + if not connection:flush() then + connection:close() + connectionThread = nil + end end) function bufferedConnection:flush() - connection:send(table.concat(self.data, "")) - self.data = {} - self.size = 0 + if self.size > 0 then + connection:send(table.concat(self.data, "")) + self.data = {} + self.size = 0 + return true + end + return false end function bufferedConnection:send(payload) local l = payload:len() - if l + self.size > 1400 then - self:flush() - coroutine.yield() + if l + self.size > 1000 then + if self:flush() then + coroutine.yield() + end + end + if l > 800 then + connection:send(payload) + coroutine.yield() + else + table.insert(self.data, payload) + self.size = self.size + l end - table.insert(self.data, payload) - self.size = self.size + l end bufferedConnection.size = 0 bufferedConnection.data = {} From a54159b39fc8828dd0db3205059e4562c8789624 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sat, 28 Nov 2015 12:23:22 -0500 Subject: [PATCH 09/15] Remove unused file --- http/node_info.lua~ | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 http/node_info.lua~ diff --git a/http/node_info.lua~ b/http/node_info.lua~ deleted file mode 100644 index 8f9d615..0000000 --- a/http/node_info.lua~ +++ /dev/null @@ -1,29 +0,0 @@ -local function sendHeader(connection) - connection:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nCache-Control: private, no-store\r\n\r\n") - -end - -local function sendAttr(connection, attr, val) - if val then - connection:send("
    • ".. attr .. ": " .. val .. "
    • \n") - end -end - -return function (connection, req, args) - collectgarbage() - sendHeader(connection) - connection:send('A Lua script sample

      Node info

        ') - local majorVer, minorVer, devVer, chipid, flashid, flashsize, flashmode, flashspeed = node.info(); - --local x = (majorVer..".")..(minorVer..".")..devVer - sendAttr(connection, "NodeMCU version" , majorVer.."."..minorVer.."."..devVer) - sendAttr(connection, "chipid" , chipid) - sendAttr(connection, "flashid" , flashid) - sendAttr(connection, "flashsize" , flashsize) - sendAttr(connection, "flashmode" , flashmode) - sendAttr(connection, "flashspeed" , flashspeed) - sendAttr(connection, "node.heap()" , node.heap()) - sendAttr(connection, 'Memory in use (KB)' , collectgarbage("count")) - sendAttr(connection, 'IP address' , wifi.sta.getip()) - sendAttr(connection, 'MAC address' , wifi.sta.getmac()) - connection:send('
      ') -end From 9fee6ee8e8ede9c314363867f00bfb774f9cfc2f Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Sat, 28 Nov 2015 16:36:53 -0500 Subject: [PATCH 10/15] Removed another unused file --- http/Thumbs.db | Bin 8192 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 http/Thumbs.db diff --git a/http/Thumbs.db b/http/Thumbs.db deleted file mode 100644 index 9707bc379f96ec6f642f124233766c24e741d296..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8192 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 Date: Sat, 28 Nov 2015 21:00:34 -0500 Subject: [PATCH 11/15] Handle the null write case --- httpserver.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/httpserver.lua b/httpserver.lua index 6695196..273eb16 100644 --- a/httpserver.lua +++ b/httpserver.lua @@ -18,9 +18,9 @@ return function (port) local function startServing(fileServeFunction, connection, req, args) local bufferedConnection = {} - connectionThread = coroutine.create(function(fileServeFunction, connection, req, args) - fileServeFunction(connection, req, args) - if not connection:flush() then + connectionThread = coroutine.create(function(fileServeFunction, bconnection, req, args) + fileServeFunction(bconnection, req, args) + if not bconnection:flush() then connection:close() connectionThread = nil end From 1b8a1f77501acbb7634206812717ac9fedf8320d Mon Sep 17 00:00:00 2001 From: IntelliDust Date: Thu, 3 Dec 2015 12:48:27 +0100 Subject: [PATCH 12/15] Added handling of watchdog for serving bigger files --- httpserver-static.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/httpserver-static.lua b/httpserver-static.lua index d7cb86f..df65c26 100644 --- a/httpserver-static.lua +++ b/httpserver-static.lua @@ -23,6 +23,7 @@ return function (connection, req, args) connection:send(chunk) bytesSent = bytesSent + #chunk chunk = nil + tmr.wdclr() -- loop can take a while for long files. tmr.wdclr() prevent watchdog to restart module --print("Sent" .. args.file, bytesSent) end end From 1f43c9f5e592139aece16c7c08ab00960a68dd97 Mon Sep 17 00:00:00 2001 From: IntelliDust Date: Thu, 3 Dec 2015 12:51:42 +0100 Subject: [PATCH 13/15] Added Cache-Control header for gzipped content. This will prevent for getting css.gz js.gz and so by browser from server. --- httpserver-header.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/httpserver-header.lua b/httpserver-header.lua index 4284dcf..62f9fe2 100644 --- a/httpserver-header.lua +++ b/httpserver-header.lua @@ -23,6 +23,7 @@ return function (connection, code, extension, gzip) connection:send("HTTP/1.0 " .. code .. " " .. getHTTPStatusString(code) .. "\r\nServer: nodemcu-httpserver\r\nContent-Type: " .. mimeType["contentType"] .. "\r\n") if gzip then + connection:send("Cache-Control: max-age=2592000\r\n") connection:send("Content-Encoding: gzip\r\n") end connection:send("Connection: close\r\n\r\n") From 02832a1a455d00beb1b1e38e5da04ac2c9947d68 Mon Sep 17 00:00:00 2001 From: Lukas Humbel Date: Sat, 19 Dec 2015 13:43:55 +0100 Subject: [PATCH 14/15] matching of mime type in http request header that contain a charset appendix --- httpserver-request.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpserver-request.lua b/httpserver-request.lua index 944617d..ae89eaa 100644 --- a/httpserver-request.lua +++ b/httpserver-request.lua @@ -51,7 +51,7 @@ local function getRequestData(payload) if requestData then return requestData else - local mimeType = string.match(payload, "Content%-Type: (%S+)\r\n") + local mimeType = string.match(payload, "Content%-Type: ([%w/-]+)") local body_start = payload:find("\r\n\r\n", 1, true) local body = payload:sub(body_start, #payload) payload = nil From 1ddd47eaf9787b7e9b5247cc1d0bd1d5592062be Mon Sep 17 00:00:00 2001 From: Anton Andersen Date: Mon, 15 Feb 2016 12:46:54 +0300 Subject: [PATCH 15/15] Fix a memory leak when the connection is dropped by the client side --- httpserver.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/httpserver.lua b/httpserver.lua index 273eb16..96bb690 100644 --- a/httpserver.lua +++ b/httpserver.lua @@ -153,6 +153,12 @@ return function (port) connection:on("receive", onReceive) connection:on("sent", onSent) + connection:on("disconnection",function(c) + if connectionThread then + connectionThread = nil + collectgarbage() + end + end) end )