httpserver-request.lua 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  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 = {}
  20. local i = 1
  21. if args == nil or args == "" then return r end
  22. for arg in string.gmatch(args, "([^&]+)") do
  23. local name, value = string.match(arg, "(.*)=(.*)")
  24. if name ~= nil then r[name] = uri_decode(value) end
  25. i = i + 1
  26. end
  27. return r
  28. end
  29. local function parseFormData(body)
  30. local data = {}
  31. --print("Parsing Form Data")
  32. for kv in body.gmatch(body, "%s*&?([^=]+=[^&]+)") do
  33. local key, value = string.match(kv, "(.*)=(.*)")
  34. --print("Parsed: " .. key .. " => " .. value)
  35. data[key] = uri_decode(value)
  36. end
  37. return data
  38. end
  39. local function getRequestData(payload)
  40. local requestData
  41. return function ()
  42. --print("Getting Request Data")
  43. -- for backward compatibility before v2.1
  44. if (sjson == nil) then
  45. sjson = cjson
  46. end
  47. if requestData then
  48. return requestData
  49. else
  50. --print("payload = [" .. payload .. "]")
  51. local mimeType = string.match(payload, "Content%-Type: ([%w/-]+)")
  52. local bodyStart = payload:find("\r\n\r\n", 1, true)
  53. local body = payload:sub(bodyStart, #payload)
  54. payload = nil
  55. collectgarbage()
  56. --print("mimeType = [" .. mimeType .. "]")
  57. --print("bodyStart = [" .. bodyStart .. "]")
  58. --print("body = [" .. body .. "]")
  59. if mimeType == "application/json" then
  60. --print("JSON: " .. body)
  61. requestData = sjson.decode(body)
  62. elseif mimeType == "application/x-www-form-urlencoded" then
  63. requestData = parseFormData(body)
  64. else
  65. requestData = {}
  66. end
  67. return requestData
  68. end
  69. end
  70. end
  71. local function parseUri(uri)
  72. local r = {}
  73. local filename
  74. local ext
  75. local fullExt = {}
  76. if uri == nil then return r end
  77. if uri == "/" then uri = "/index.html" end
  78. local questionMarkPos, b, c, d, e, f = uri:find("?")
  79. if questionMarkPos == nil then
  80. r.file = uri:sub(1, questionMarkPos)
  81. r.args = {}
  82. else
  83. r.file = uri:sub(1, questionMarkPos - 1)
  84. r.args = parseArgs(uri:sub(questionMarkPos+1, #uri))
  85. end
  86. filename = r.file
  87. while filename:match("%.") do
  88. filename,ext = filename:match("(.+)%.(.+)")
  89. table.insert(fullExt,1,ext)
  90. end
  91. if #fullExt > 1 and fullExt[#fullExt] == 'gz' then
  92. r.ext = fullExt[#fullExt-1]
  93. r.isGzipped = true
  94. elseif #fullExt >= 1 then
  95. r.ext = fullExt[#fullExt]
  96. end
  97. r.isScript = r.ext == "lua" or r.ext == "lc"
  98. r.file = uriToFilename(r.file)
  99. return r
  100. end
  101. -- Parses the client's request. Returns a dictionary containing pretty much everything
  102. -- the server needs to know about the uri.
  103. return function (request)
  104. --print("Request: \n", request)
  105. local e = request:find("\r\n", 1, true)
  106. if not e then return nil end
  107. local line = request:sub(1, e - 1)
  108. local r = {}
  109. local _, i
  110. _, i, r.method, r.request = line:find("^([A-Z]+) (.-) HTTP/[1-9]+.[0-9]+$")
  111. if not (r.method and r.request) then
  112. --print("invalid request: ")
  113. --print(request)
  114. return nil
  115. end
  116. r.methodIsValid = validateMethod(r.method)
  117. r.uri = parseUri(r.request)
  118. r.getRequestData = getRequestData(request)
  119. return r
  120. end