From 2f2fb26782c56ab27dc318fec9115bc8f2504b38 Mon Sep 17 00:00:00 2001 From: Marcos Kirsch Date: Mon, 15 Feb 2016 22:52:30 -0600 Subject: [PATCH] Several fixes and code cleanup. Gzipped files now work. Indentation cleaned up, Other small fixes: --- httpserver-header.lua | 14 +++--- httpserver-request.lua | 66 ++++++++++++++-------------- httpserver-static.lua | 7 +-- httpserver.lua | 98 ++++++++++++++++++++++-------------------- 4 files changed, 92 insertions(+), 93 deletions(-) diff --git a/httpserver-header.lua b/httpserver-header.lua index 6d44bfd..9f8f7bc 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, gzip) +return function (connection, code, extension, isGzipped) local function getHTTPStatusString(code) local codez = {[200]="OK", [400]="Bad Request", [404]="Not Found",} @@ -12,21 +12,21 @@ return function (connection, code, extension, gzip) end local function getMimeType(ext) - 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"} - if mt[ext] then contentType = mt[ext] else contentType = "text/plain" end - return {contentType = contentType, gzip = gzip} + if mt[ext] then return mt[ext] else return "text/plain" end end local mimeType = getMimeType(extension) - local header = "HTTP/1.0 " .. code .. " " .. getHTTPStatusString(code) .. "\r\nServer: nodemcu-httpserver\r\nContent-Type: " .. mimeType["contentType"] .. "\r\n" - if mimeType["gzip"] then header = header .. "Content-Encoding: gzip\r\n" end + local header = "HTTP/1.0 " .. code .. " " .. getHTTPStatusString(code) .. "\r\nServer: nodemcu-httpserver\r\nContent-Type: " .. mimeType .. "\r\nnCache-Control: private, no-store\r\n" + if isGzipped then + header = header .. "Cache-Control: max-age=2592000\r\n" + header = header .. "Content-Encoding: gzip\r\n" + end header = header .. "Connection: close\r\n\r\n" connection:send(header) header = nil - coroutine.yield() end diff --git a/httpserver-request.lua b/httpserver-request.lua index 9f77620..bf51d49 100644 --- a/httpserver-request.lua +++ b/httpserver-request.lua @@ -32,45 +32,43 @@ local function parseArgs(args) 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 + 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: ([%w/-]+)") - 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) + local requestData + return function () + --print("Getting Request Data") + if requestData then + return requestData else - requestData = {} + --print("payload = [" .. payload .. "]") + local mimeType = string.match(payload, "Content%-Type: ([%w/-]+)") + local bodyStart = payload:find("\r\n\r\n", 1, true) + local body = payload:sub(bodyStart, #payload) + payload = nil + collectgarbage() + --print("mimeType = [" .. mimeType .. "]") + --print("bodyStart = [" .. bodyStart .. "]") + --print("body = [" .. body .. "]") + 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 - - return requestData - end - end + end end local function parseUri(uri) diff --git a/httpserver-static.lua b/httpserver-static.lua index 0cd7ee1..9140b86 100644 --- a/httpserver-static.lua +++ b/httpserver-static.lua @@ -3,8 +3,8 @@ -- Author: Marcos Kirsch return function (connection, req, args) - dofile("httpserver-header.lc")(connection, 200, args.ext, args.gzipped) --print("Begin sending:", args.file) + dofile("httpserver-header.lc")(connection, 200, args.ext, args.isGzipped) -- Send file in little chunks local continue = true local size = file.list()[args.file] @@ -24,10 +24,7 @@ return function (connection, req, args) bytesSent = bytesSent + #chunk chunk = nil --print("Sent: " .. bytesSent .. " of " .. size) - -- nodemcu-firmware disallows queueing send operations, - -- so we must call coroutine.yield() after every connection:send() - -- The onSent() will resume us. But only yield if we aren't done yet! - if bytesSent == size then continue = false else coroutine.yield() end + if bytesSent == size then continue = false end end --print("Finished sending: ", args.file) end diff --git a/httpserver.lua b/httpserver.lua index 96bb690..5c32039 100644 --- a/httpserver.lua +++ b/httpserver.lua @@ -13,47 +13,51 @@ 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 function startServing(fileServeFunction, connection, req, args) + local bufferedConnection = {} connectionThread = coroutine.create(function(fileServeFunction, bconnection, req, args) fileServeFunction(bconnection, req, args) if not bconnection:flush() then - connection:close() - connectionThread = nil - end + connection:close() + connectionThread = nil + end end) - function bufferedConnection:flush() - if self.size > 0 then - connection:send(table.concat(self.data, "")) - self.data = {} - self.size = 0 - return true - end - return false + + function bufferedConnection:flush() + 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 > 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 + + function bufferedConnection:send(payload) + local l = payload:len() + 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 end + bufferedConnection.size = 0 bufferedConnection.data = {} local status, err = coroutine.resume(connectionThread, fileServeFunction, bufferedConnection, req, args) if not status then - print(err) + print("Error: ", err) end end @@ -62,9 +66,9 @@ return function (port) local method = req.method local uri = req.uri local fileServeFunction = nil - - print("Method: " .. method); - + + --print("Method: " .. method); + if #(uri.file) > 32 then -- nodemcu-firmware cannot handle long filenames. uri.args = {code = 400, errorString = "Bad Request"} @@ -72,17 +76,17 @@ return function (port) 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 fileExists then - print("gzip variant exists, serving that one") - uri.file = uri.file .. ".gz" - uri.isGzipped = true - end + 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.isGzipped = true + end end if not fileExists then @@ -92,11 +96,11 @@ return function (port) fileServeFunction = dofile(uri.file) else if allowStatic[method] then - uri.args = {file = uri.file, ext = uri.ext, gzipped = uri.isGzipped} - fileServeFunction = dofile("httpserver-static.lc") + uri.args = {file = uri.file, ext = uri.ext, isGzipped = uri.isGzipped} + fileServeFunction = dofile("httpserver-static.lc") else - uri.args = {code = 405, errorString = "Method not supported"} - fileServeFunction = dofile("httpserver-error.lc") + uri.args = {code = 405, errorString = "Method not supported"} + fileServeFunction = dofile("httpserver-error.lc") end end end @@ -136,7 +140,7 @@ return function (port) 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. local status, err = coroutine.resume(connectionThread) @@ -158,7 +162,7 @@ return function (port) connectionThread = nil collectgarbage() end - end) + end) end )