httpd.lua 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. local internal = require "http.internal"
  2. local table = table
  3. local string = string
  4. local type = type
  5. local httpd = {}
  6. local http_status_msg = {
  7. [100] = "Continue",
  8. [101] = "Switching Protocols",
  9. [200] = "OK",
  10. [201] = "Created",
  11. [202] = "Accepted",
  12. [203] = "Non-Authoritative Information",
  13. [204] = "No Content",
  14. [205] = "Reset Content",
  15. [206] = "Partial Content",
  16. [300] = "Multiple Choices",
  17. [301] = "Moved Permanently",
  18. [302] = "Found",
  19. [303] = "See Other",
  20. [304] = "Not Modified",
  21. [305] = "Use Proxy",
  22. [307] = "Temporary Redirect",
  23. [400] = "Bad Request",
  24. [401] = "Unauthorized",
  25. [402] = "Payment Required",
  26. [403] = "Forbidden",
  27. [404] = "Not Found",
  28. [405] = "Method Not Allowed",
  29. [406] = "Not Acceptable",
  30. [407] = "Proxy Authentication Required",
  31. [408] = "Request Time-out",
  32. [409] = "Conflict",
  33. [410] = "Gone",
  34. [411] = "Length Required",
  35. [412] = "Precondition Failed",
  36. [413] = "Request Entity Too Large",
  37. [414] = "Request-URI Too Large",
  38. [415] = "Unsupported Media Type",
  39. [416] = "Requested range not satisfiable",
  40. [417] = "Expectation Failed",
  41. [500] = "Internal Server Error",
  42. [501] = "Not Implemented",
  43. [502] = "Bad Gateway",
  44. [503] = "Service Unavailable",
  45. [504] = "Gateway Time-out",
  46. [505] = "HTTP Version not supported",
  47. }
  48. local function readall(readbytes, bodylimit)
  49. local tmpline = {}
  50. local body = internal.recvheader(readbytes, tmpline, "")
  51. if not body then
  52. return 413 -- Request Entity Too Large
  53. end
  54. local request = assert(tmpline[1])
  55. local method, url, httpver = request:match "^(%a+)%s+(.-)%s+HTTP/([%d%.]+)$"
  56. assert(method and url and httpver)
  57. httpver = assert(tonumber(httpver))
  58. if httpver < 1.0 or httpver > 1.1 then
  59. return 505 -- HTTP Version not supported
  60. end
  61. local header = internal.parseheader(tmpline,2,{})
  62. if not header then
  63. return 400 -- Bad request
  64. end
  65. local length = header["content-length"]
  66. if length then
  67. length = tonumber(length)
  68. end
  69. local mode = header["transfer-encoding"]
  70. if mode then
  71. if mode ~= "identity" and mode ~= "chunked" then
  72. return 501 -- Not Implemented
  73. end
  74. end
  75. if mode == "chunked" then
  76. body, header = internal.recvchunkedbody(readbytes, bodylimit, header, body)
  77. if not body then
  78. return 413
  79. end
  80. else
  81. -- identity mode
  82. if length then
  83. if bodylimit and length > bodylimit then
  84. return 413
  85. end
  86. if #body >= length then
  87. body = body:sub(1,length)
  88. else
  89. local padding = readbytes(length - #body)
  90. body = body .. padding
  91. end
  92. end
  93. end
  94. return 200, url, method, header, body
  95. end
  96. function httpd.read_request(...)
  97. local ok, code, url, method, header, body = pcall(readall, ...)
  98. if ok then
  99. return code, url, method, header, body
  100. else
  101. return nil, code
  102. end
  103. end
  104. local function writeall(writefunc, statuscode, bodyfunc, header)
  105. local statusline = string.format("HTTP/1.1 %03d %s\r\n", statuscode, http_status_msg[statuscode] or "")
  106. writefunc(statusline)
  107. if header then
  108. for k,v in pairs(header) do
  109. if type(v) == "table" then
  110. for _,v in ipairs(v) do
  111. writefunc(string.format("%s: %s\r\n", k,v))
  112. end
  113. else
  114. writefunc(string.format("%s: %s\r\n", k,v))
  115. end
  116. end
  117. end
  118. local t = type(bodyfunc)
  119. if t == "string" then
  120. writefunc(string.format("content-length: %d\r\n\r\n", #bodyfunc))
  121. writefunc(bodyfunc)
  122. elseif t == "function" then
  123. writefunc("transfer-encoding: chunked\r\n")
  124. while true do
  125. local s = bodyfunc()
  126. if s then
  127. if s ~= "" then
  128. writefunc(string.format("\r\n%x\r\n", #s))
  129. writefunc(s)
  130. end
  131. else
  132. writefunc("\r\n0\r\n\r\n")
  133. break
  134. end
  135. end
  136. else
  137. assert(t == "nil")
  138. writefunc("\r\n")
  139. end
  140. end
  141. function httpd.write_response(...)
  142. return pcall(writeall, ...)
  143. end
  144. return httpd