Advertisement
Guest User

Untitled

a guest
Jan 31st, 2021
365
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 9.52 KB | None | 0 0
  1. local cqueues = require "cqueues"
  2. local socket  = require "cqueues.socket"
  3.  
  4. -- usage example:
  5. -- local server = require 'http-server'
  6. -- server:router{
  7. --     {['/'] = function(req)  return 'it works.' end},
  8. -- }
  9. -- server:listen{host='0.0.0.0', port=8000)
  10. -- server:start()
  11.  
  12. -- main loop
  13. local main = cqueues.new()
  14.  
  15. -- log levels
  16. local LOG_LEVEL = {
  17.     'CRIT', 'WARN', 'INFO', 'DEBUG',
  18.     CRIT  = 1,
  19.     WARN  = 2,
  20.     INFO  = 3,
  21.     DEBUG = 4,
  22. }
  23.  
  24. -- http codes
  25. local HTTP_CODE = {
  26.         [100] = "Continue",
  27.         [101] = "Switching Protocols",
  28.         [102] = "Processing",
  29.         [103] = "Early Hints",
  30.         [200] = "OK",
  31.         [201] = "Created",
  32.         [202] = "Accepted",
  33.         [203] = "Non-Authoritative Information",
  34.         [204] = "No Content",
  35.         [205] = "Reset Content",
  36.         [206] = "Partial Content",
  37.         [207] = "Multi-Status",
  38.         [208] = "Already Reported",
  39.         [226] = "IM Used",
  40.         [300] = "Multiple Choices",
  41.         [301] = "Moved Permanently",
  42.         [302] = "Found",
  43.         [303] = "See Other",
  44.         [304] = "Not Modified",
  45.         [305] = "Use Proxy",
  46.         [307] = "Temporary Redirect",
  47.         [308] = "Permanent Redirect",
  48.         [400] = "Bad Request",
  49.         [401] = "Unauthorized",
  50.         [402] = "Payment Required",
  51.         [403] = "Forbidden",
  52.         [404] = "Not Found",
  53.         [405] = "Method Not Allowed",
  54.         [406] = "Not Acceptable",
  55.         [407] = "Proxy Authentication Required",
  56.         [408] = "Request Timeout",
  57.         [409] = "Conflict",
  58.         [410] = "Gone",
  59.         [411] = "Length Required",
  60.         [412] = "Precondition Failed",
  61.         [413] = "Request Entity Too Large",
  62.         [414] = "Request-URI Too Long",
  63.         [415] = "Unsupported Media Type",
  64.         [416] = "Requested Range Not Satisfiable",
  65.         [417] = "Expectation Failed",
  66.         [418] = "I'm a teapot",
  67.         [420] = "Content was smoked",
  68.         [421] = "Misdirected Request",
  69.         [422] = "Unprocessable Entity",
  70.         [423] = "Locked",
  71.         [424] = "Failed Dependency",
  72.         [426] = "Upgrade Required",
  73.         [428] = "Precondition Required",
  74.         [429] = "Too Many Requests",
  75.         [431] = "Request Header Fields Too Large",
  76.         [451] = "Unavailable For Legal Reasons",
  77.         [500] = "Internal Server Error",
  78.         [501] = "Not Implemented",
  79.         [502] = "Bad Gateway",
  80.         [503] = "Service Unavailable",
  81.         [504] = "Gateway Timeout",
  82.         [505] = "HTTP Version Not Supported",
  83.         [506] = "Variant Also Negotiates",
  84.         [507] = "Insufficient Storage",
  85.         [508] = "Loop Detected",
  86.         [510] = "Not Extended",
  87.         [511] = "Network Authentication Required",
  88. }
  89.  
  90.  
  91. -- virtual server object
  92. local server = {
  93.     loglevel = LOG_LEVEL.INFO, -- log level
  94.     count    = 0,   -- conn counter
  95.     conn     = nil, -- socket  
  96.     peer     = nil, -- info
  97.     listener = { -- listen settings
  98.         host = '0.0.0.0',
  99.         port = 8000,
  100.         reuseaddr = true,
  101.         reuseport = false,  
  102.     },
  103.     content_type = 'application/octet-stream', -- default content type
  104.     timeout = 10, -- read timeout (10 sec.)
  105.     max_request_len = 1024*1024*10, -- request size limit (max. 10 mb)    
  106.     routes = {  -- routes
  107.         DEFAULT = function() end,
  108.         ERROR   = {
  109.             [404] = function(req) return 'not found' end,
  110.         },
  111.         HEAD    = {},  
  112.         GET     = {},
  113.         POST    = {},
  114.         PUT     = {},
  115.         DELETE  = {},
  116.         OPTIONS = {},
  117.     },
  118. }
  119.  
  120. -- logger
  121. function server:log(level, ...)
  122.     local level  = LOG_LEVEL[level:upper()] or 4
  123.     local output = (level > 2) and io.stdout or io.stderr
  124.     if self.loglevel >= level then output:write(string.format("[%s] [%s]: %s", os.date(), LOG_LEVEL[level], string.format(...)), "\n") end -- write log
  125.     if level == 1 then os.exit() end  -- crit
  126. end
  127.  
  128. -- peer info
  129. -- you can use it to obtain peer IP and port from conn:peerinfo() or conn:localname()
  130. function server:peerinfo(...)
  131.     local _, ip, port = ...
  132.     return {ip=ip, port=port}    
  133. end
  134.  
  135. -- router example
  136. --
  137. -- server:router{
  138. --    {['/'] = function(req)  return 'it works.' end}, -- / (any method)
  139. --    {'GET',  ['/hello'] = function(req) return 'it works.' end},  -- /hello (GET only)
  140. --    {'POST', ['/post']  = function(req) return 'oh, you posted ' .. (req.body or '') end}, -- /post (POST only)
  141. -- }
  142. --
  143. -- inside router, we have `req` object:
  144. --
  145. -- {
  146. --  id = 1, -- request unique id
  147. --  conn = <userdata 1>, -- connection object
  148. --  peer = {
  149. --    ip = "127.0.0.1",
  150. --    port = 41810,
  151. --  }
  152. --  method = "GET", -- request method
  153. --  uri = "/inspect", -- request uri
  154. --  version = "1.1", -- http version
  155. --  headers = { -- request headers
  156. --    accept = "*/*",
  157. --    ["accept-encoding"] = "identity",
  158. --    connection = "Keep-Alive",
  159. --    host = "localhost:8000",
  160. --    ["user-agent"] = "Wget/1.20.3 (linux-gnu)"
  161. --  },
  162. --  body = "",  -- request body
  163. --  status = 200, -- response status
  164. --  header = { -- response headers
  165. --    ["Content-type"] = "application/octet-stream"
  166. --  },
  167. --  response = nil, -- response body
  168. -- }
  169. function server:router(...)
  170.     setmetatable(self.routes, {
  171.         __call  = function(self, ...)
  172.             for _,route in pairs(...) do
  173.                 local method = (route[1]) and {table.remove(route)} or {'HEAD','GET','POST','PUT','DELETE','OPTIONS'}
  174.                 local uri, handler = next(route)
  175.                 for _,method in pairs(method) do self[method][uri] = handler end
  176.             end
  177.             return self
  178.         end,
  179.     })
  180.     return self.routes(...)
  181. end
  182.  
  183. -- accept new connection
  184. function server:accept(conn)
  185.     local request = {conn=conn, id=self.count, peer=self:peerinfo(conn:peername()), method=nil, uri=nil, version=nil, headers={}, body="", route=nil, status=200, header={['Content-type']=self.content_type}, response=nil}
  186.     self:log('debug', '(%d) accepted connection from peer %s:%d', request.id, request.peer.ip, request.peer.port)
  187.  
  188.     -- set mode & timeout
  189.     conn:setmode("tl", "tf") -- ("b", "bf") for binary
  190.     conn:settimeout(self.timeout)
  191.  
  192.     -- read request
  193.     local line, err = conn:read("*l")
  194.     if not line then
  195.         self:log('warn', '(%d) no request received (%s)', request.id, err)
  196.         return conn:close()
  197.     end
  198.    
  199.     -- parse request
  200.     request.method, request.uri, request.version = string.match(line, "^(%w+) (%S+) HTTP/(1%.[01])$")
  201.     request.route = self.routes[request.method][request.uri]
  202.     if not request.uri or not self.routes[request.method] then  -- malformed request
  203.         self:log('warn', '(%d) malformed request received', request.id)
  204.         return conn:close()
  205.     elseif not request.route then  -- not found
  206.         self:log('warn', '(%d) route does not exist', request.id)
  207.         request.status = 404
  208.     end
  209.     -- self:log('info', string.format('(%d) %s %s %s', request.id, request.method, request.uri, request.version))
  210.    
  211.     -- parse headers
  212.     request.headers = {}
  213.     for header in conn:lines("*h") do  -- read headers
  214.         local key, value = string.match(header, "^([^:%s]+)%s*:%s*(.*)$") -- parse header
  215.         if key and value then request.headers[key:lower()] = value end -- append header to headers table
  216.     end
  217.     conn:read("*l") -- skip double newline
  218.  
  219.     -- parse body
  220.     request.length = tonumber(request.headers['content-length']) or 0
  221.     if self.max_request_len >= request.length then -- seems legit
  222.         request.body = conn:read(request.length)
  223.     else -- evil hackers!
  224.         self:log('warn', '(%d) size limit exceed', request.id)
  225.         request.status = 413
  226.     end
  227.  
  228.     -- route request
  229.     request.route = (request.status < 400) and request.route or self.routes.ERROR[request.status] or self.routes.DEFAULT -- use ERROR routes when status >= 400
  230.     _, request.response = pcall(request.route, request)
  231.     if not _ then  -- pcall failed (route handler crashed)
  232.         self:log('warn', '(%d) route handler crashed (%s)', request.id, request.response)
  233.         request.status = 503
  234.     end
  235.  
  236.     -- conjure response
  237.     conn:write(string.format('HTTP/%.1f %d %s\n', request.version, request.status, HTTP_CODE[request.status]))
  238.     for header, value in pairs(request.header or {}) do
  239.         conn:write(string.format("%s: %s\n", header, value))
  240.     end
  241.     conn:write("\n")
  242.     conn:write(request.response or "")
  243.     conn:flush()
  244.     conn:shutdown()
  245.     conn:close()
  246.     self:log('debug', '(%d) connection closed', request.id)
  247. end
  248.  
  249. -- accepts options that are valid to cqueues socket.listen (ref: http://25thandclement.com/~william/projects/cqueues.pdf)
  250. -- example:
  251. --
  252. -- server:listen{host='0.0.0.0', port=8000)
  253. function server:listen(options)
  254.     -- set options
  255.     for key, value in pairs(options or {}) do self.listener[key] = value end
  256.     -- bind socket
  257.     self.conn = socket.listen(self.listener)
  258.     self.peer = self:peerinfo(self.conn:localname())
  259.     -- ensure socket is opened
  260.     assert(self.peer.port, 'Failed to bind: ERRNO '..self.peer.ip)
  261.     self:log('info', 'server listening on %s:%d', self.peer.ip, self.peer.port)
  262. end
  263.  
  264. -- run the main loop
  265. -- you need to call it when all is set up
  266. function server:start()
  267.     self:log('info', 'waiting for new connections')
  268.     main:wrap(function()
  269.         for conn in self.conn:clients() do
  270.             self.count = self.count + 1
  271.             main:wrap(self.accept, self, conn)
  272.         end
  273.     end)    
  274.     main:loop()
  275. end
  276.  
  277. --
  278. return server
  279.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement