httpd.lua 3.6 KB

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