httpserver-request.lua 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. -- httpserver-request
  2. -- Part of nodemcu-httpserver, parses incoming client requests.
  3. -- Author: Marcos Kirsch
  4. local function validateMethod(method)
  5. local httpMethods = {GET=true, HEAD=true, POST=true, PUT=true, DELETE=true, TRACE=true, OPTIONS=true, CONNECT=true, PATCH=true}
  6. -- default for non-existent attributes returns nil, which evaluates to false
  7. return httpMethods[method]
  8. end
  9. local function uriToFilename(uri)
  10. return "http/" .. string.sub(uri, 2, -1)
  11. end
  12. local function hex_to_char(x)
  13. return string.char(tonumber(x, 16))
  14. end
  15. local function uri_decode(input)
  16. return input:gsub("%+", " "):gsub("%%(%x%x)", hex_to_char)
  17. end
  18. local function parseArgs(args)
  19. local r = {}; i=1
  20. if args == nil or args == "" then return r end
  21. for arg in string.gmatch(args, "([^&]+)") do
  22. local name, value = string.match(arg, "(.*)=(.*)")
  23. if name ~= nil then r[name] = uri_decode(value) end
  24. i = i + 1
  25. end
  26. return r
  27. end
  28. local function parseFormData(body)
  29. local data = {}
  30. --print("Parsing Form Data")
  31. for kv in body.gmatch(body, "%s*&?([^=]+=[^&]+)") do
  32. local key, value = string.match(kv, "(.*)=(.*)")
  33. --print("Parsed: " .. key .. " => " .. value)
  34. data[key] = uri_decode(value)
  35. end
  36. return data
  37. end
  38. local function getRequestData(payload)
  39. local requestData
  40. return function ()
  41. --print("Getting Request Data")
  42. if requestData then
  43. return requestData
  44. else
  45. --print("payload = [" .. payload .. "]")
  46. local mimeType = string.match(payload, "Content%-Type: ([%w/-]+)")
  47. local bodyStart = payload:find("\r\n\r\n", 1, true)
  48. local body = payload:sub(bodyStart, #payload)
  49. payload = nil
  50. collectgarbage()
  51. --print("mimeType = [" .. mimeType .. "]")
  52. --print("bodyStart = [" .. bodyStart .. "]")
  53. --print("body = [" .. body .. "]")
  54. if mimeType == "application/json" then
  55. --print("JSON: " .. body)
  56. requestData = cjson.decode(body)
  57. elseif mimeType == "application/x-www-form-urlencoded" then
  58. requestData = parseFormData(body)
  59. else
  60. requestData = {}
  61. end
  62. return requestData
  63. end
  64. end
  65. end
  66. local function parseUri(uri)
  67. local r = {}
  68. local filename
  69. local ext
  70. local fullExt = {}
  71. if uri == nil then return r end
  72. if uri == "/" then uri = "/index.html" end
  73. questionMarkPos, b, c, d, e, f = uri:find("?")
  74. if questionMarkPos == nil then
  75. r.file = uri:sub(1, questionMarkPos)
  76. r.args = {}
  77. else
  78. r.file = uri:sub(1, questionMarkPos - 1)
  79. r.args = parseArgs(uri:sub(questionMarkPos+1, #uri))
  80. end
  81. filename = r.file
  82. while filename:match("%.") do
  83. filename,ext = filename:match("(.+)%.(.+)")
  84. table.insert(fullExt,1,ext)
  85. end
  86. if #fullExt > 1 and fullExt[#fullExt] == 'gz' then
  87. r.ext = fullExt[#fullExt-1]
  88. r.isGzipped = true
  89. elseif #fullExt >= 1 then
  90. r.ext = fullExt[#fullExt]
  91. end
  92. r.isScript = r.ext == "lua" or r.ext == "lc"
  93. r.file = uriToFilename(r.file)
  94. return r
  95. end
  96. -- Parses the client's request. Returns a dictionary containing pretty much everything
  97. -- the server needs to know about the uri.
  98. return function (request)
  99. --print("Request: \n", request)
  100. local e = request:find("\r\n", 1, true)
  101. if not e then return nil end
  102. local line = request:sub(1, e - 1)
  103. local r = {}
  104. _, i, r.method, r.request = line:find("^([A-Z]+) (.-) HTTP/[1-9]+.[0-9]+$")
  105. r.methodIsValid = validateMethod(r.method)
  106. r.uri = parseUri(r.request)
  107. r.getRequestData = getRequestData(request)
  108. return r
  109. end