Snusmumriken

Luajit/5.2+ http server

Jan 13th, 2019 (edited)
164
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.78 KB | None | 0 0
  1. local socket   = require'socket'
  2.  
  3. -- cds - canonic code reason
  4. local ltn, uri
  5.  
  6. if ... then
  7.     ltn = require'sock.ltn'
  8.     uri = require'sock.uri'
  9. else -- testing
  10.     ltn = require'ltn'
  11.     uri = require'uri'
  12. end
  13.  
  14. local function touch(filepath)
  15.     local file = io.open(filepath, 'rb')
  16.     if not file then
  17.         return false
  18.     end
  19.     return true, file:close()
  20. end
  21.  
  22. local function formatnum(v)
  23.     v = tonumber(v) or 0
  24.     v = tostring(v)
  25.     return v:reverse():gsub('%d%d%d', '%1\''):reverse():match('%d.*')
  26. end
  27.  
  28.  
  29. local function routine(func)
  30.     return coroutine.wrap(function(...)
  31.         local res = {xpcall(func, debug.traceback, ...)}
  32.         if res[1] then
  33.             table.remove(res, 1)
  34.             return unpack(res)
  35.         end
  36.         error(res[2], 2)
  37.     end)
  38. end
  39.  
  40. local Headers = {
  41.     ["accept"]                    = "Accept",
  42.     ["accept-charset"]            = "Accept-Charset",
  43.     ["accept-encoding"]           = "Accept-Encoding",
  44.     ["accept-language"]           = "Accept-Language",
  45.     ["accept-ranges"]             = "Accept-Ranges",
  46.     ["action"]                    = "Action",
  47.     ["alternate-recipient"]       = "Alternate-Recipient",
  48.     ["age"]                       = "Age",
  49.     ["allow"]                     = "Allow",
  50.     ["arrival-date"]              = "Arrival-Date",
  51.     ["authorization"]             = "Authorization",
  52.     ["bcc"]                       = "Bcc",
  53.     ["cache-control"]             = "Cache-Control",
  54.     ["cc"]                        = "Cc",
  55.     ["comments"]                  = "Comments",
  56.     ["connection"]                = "Connection",
  57.     ["content-description"]       = "Content-Description",
  58.     ["content-disposition"]       = "Content-Disposition",
  59.     ["content-encoding"]          = "Content-Encoding",
  60.     ["content-id"]                = "Content-ID",
  61.     ["content-language"]          = "Content-Language",
  62.     ["content-length"]            = "Content-Length",
  63.     ["content-location"]          = "Content-Location",
  64.     ["content-md5"]               = "Content-MD5",
  65.     ["content-range"]             = "Content-Range",
  66.     ["content-transfer-encoding"] = "Content-Transfer-Encoding",
  67.     ["content-type"]              = "Content-Type",
  68.     ["cookie"]                    = "Cookie",
  69.     ["date"]                      = "Date",
  70.     ["diagnostic-code"]           = "Diagnostic-Code",
  71.     ["dsn-gateway"]               = "DSN-Gateway",
  72.     ["etag"]                      = "ETag",
  73.     ["expect"]                    = "Expect",
  74.     ["expires"]                   = "Expires",
  75.     ["final-log-id"]              = "Final-Log-ID",
  76.     ["final-recipient"]           = "Final-Recipient",
  77.     ["from"]                      = "From",
  78.     ["host"]                      = "Host",
  79.     ["if-match"]                  = "If-Match",
  80.     ["if-modified-since"]         = "If-Modified-Since",
  81.     ["if-none-match"]             = "If-None-Match",
  82.     ["if-range"]                  = "If-Range",
  83.     ["if-unmodified-since"]       = "If-Unmodified-Since",
  84.     ["in-reply-to"]               = "In-Reply-To",
  85.     ["keywords"]                  = "Keywords",
  86.     ["last-attempt-date"]         = "Last-Attempt-Date",
  87.     ["last-modified"]             = "Last-Modified",
  88.     ["location"]                  = "Location",
  89.     ["max-forwards"]              = "Max-Forwards",
  90.     ["message-id"]                = "Message-ID",
  91.     ["mime-version"]              = "MIME-Version",
  92.     ["original-envelope-id"]      = "Original-Envelope-ID",
  93.     ["original-recipient"]        = "Original-Recipient",
  94.     ["pragma"]                    = "Pragma",
  95.     ["proxy-authenticate"]        = "Proxy-Authenticate",
  96.     ["proxy-authorization"]       = "Proxy-Authorization",
  97.     ["range"]                     = "Range",
  98.     ["received"]                  = "Received",
  99.     ["received-from-mta"]         = "Received-From-MTA",
  100.     ["references"]                = "References",
  101.     ["referer"]                   = "Referer",
  102.     ["remote-mta"]                = "Remote-MTA",
  103.     ["reply-to"]                  = "Reply-To",
  104.     ["reporting-mta"]             = "Reporting-MTA",
  105.     ["resent-bcc"]                = "Resent-Bcc",
  106.     ["resent-cc"]                 = "Resent-Cc",
  107.     ["resent-date"]               = "Resent-Date",
  108.     ["resent-from"]               = "Resent-From",
  109.     ["resent-message-id"]         = "Resent-Message-ID",
  110.     ["resent-reply-to"]           = "Resent-Reply-To",
  111.     ["resent-sender"]             = "Resent-Sender",
  112.     ["resent-to"]                 = "Resent-To",
  113.     ["retry-after"]               = "Retry-After",
  114.     ["return-path"]               = "Return-Path",
  115.     ["sender"]                    = "Sender",
  116.     ["server"]                    = "Server",
  117.     ["smtp-remote-recipient"]     = "SMTP-Remote-Recipient",
  118.     ["status"]                    = "Status",
  119.     ["subject"]                   = "Subject",
  120.     ["te"]                        = "TE",
  121.     ["to"]                        = "To",
  122.     ["trailer"]                   = "Trailer",
  123.     ["transfer-encoding"]         = "Transfer-Encoding",
  124.     ["upgrade"]                   = "Upgrade",
  125.     ["user-agent"]                = "User-Agent",
  126.     ["vary"]                      = "Vary",
  127.     ["via"]                       = "Via",
  128.     ["warning"]                   = "Warning",
  129.     ["will-retry-until"]          = "Will-Retry-Until",
  130.     ["www-authenticate"]          = "WWW-Authenticate",
  131.     ["x-mailer"]                  = "X-Mailer",
  132. }
  133.  
  134. local Codes = {
  135.     -- INFO
  136.     [100] = 'Continue',
  137.     [101] = 'Switchibg Protocols',
  138.     [102] = 'Processing',
  139.    
  140.     -- SUCCESS
  141.     [200] = 'OK',
  142.     [201] = 'Created',
  143.     [202] = 'Accepted',
  144.     [203] = 'Non-Authoritative Information',
  145.     [204] = 'No Content',
  146.     [205] = 'Reset Content',
  147.     [206] = 'Partial Content',
  148.     [207] = 'Multi-Status',
  149.     [208] = 'Already Reported',
  150.     [226] = 'IM Used',
  151.    
  152.     -- REDIRECT
  153.     [300] = 'Multiple Choices',
  154.     [301] = 'Moved Permanently',
  155.     [302] = 'Moved Temporarily',
  156.     [302] = 'Found', -- hehe
  157.     [303] = 'See Other',
  158.     [304] = 'Not Modified',
  159.     [305] = 'Use Proxy',
  160.     [306] = 'Reserved',
  161.     [307] = 'Temporary Redirect',
  162.     [308] = 'Permanent Redirect',
  163.    
  164.     -- REQUEST ERROR
  165.     [400] = 'Bad Request',
  166.     [401] = 'Unauthorized',
  167.     [402] = 'Payment Required',
  168.     [403] = 'Forbidden',
  169.     [404] = 'Not Found',
  170.     [405] = 'Method Not Allowed',
  171.     [406] = 'Not Acceptable',
  172.     [407] = 'Proxy Authentication Required',
  173.     [408] = 'Request Timeout',
  174.     [409] = 'Conflict',
  175.     [410] = 'Gone',
  176.     [411] = 'Length Required',
  177.     [412] = 'Precondition Failed',
  178.     [413] = 'Payload Too Large',
  179.     [414] = 'URI Too Long',
  180.     [415] = 'Unsupported Media Type',
  181.     [416] = 'Range Not Satisfiable',
  182.     [417] = 'Expectation Failed',
  183.     [418] = 'I\'m a teapot',
  184.     [419] = 'Authentication Timeout',
  185.     [421] = 'Misdirected Request',
  186.     [422] = 'Unprocessable Entity',
  187.     [423] = 'Locked ',
  188.     [424] = 'Failed Dependency',
  189.     [426] = 'Upgrade Required',
  190.     [428] = 'Precondition Required',
  191.     [429] = 'Too Many Requests',
  192.     [431] = 'Request Header Fields Too Large',
  193.     [434] = 'Requested host unavailable',
  194.     [449] = 'Retry With',
  195.     [451] = 'Unavailable For Legal Reasons',
  196.     [499] = 'Client Closed Request',
  197.    
  198.     -- SERVER ERROR
  199.     [500] = 'Internal Server Error',
  200.     [501] = 'Not Implemented',
  201.     [502] = 'Bad Gateway',
  202.     [503] = 'Service Unavailable',
  203.     [504] = 'Gateway Timeout',
  204.     [505] = 'HTTP Version Not Supported',
  205.     [506] = 'Variant Also Negotiates',
  206.     [507] = 'Insufficient Storage',
  207.     [509] = 'Bandwidth Limit Exceeded',
  208.     [510] = 'Not Extended',
  209.     [511] = 'Network Authentication Required',
  210.     [521] = 'Web Server Is Down',
  211.     [522] = 'Connection Timed Out',
  212.     [523] = 'Origin Is Unreachable',
  213.     [525] = 'SSL Handshake Failed',
  214.     [526] = 'Invalid SSL Certificate'
  215. }
  216.  
  217. local client = {}
  218. client.__index = client
  219. client.timeout = 60
  220.  
  221. client.maxheader = 1048576 * 0.5 -- 0.5mb
  222. client.fileroot  = os.getenv('')
  223. client.filebody  = 1048576 * 5
  224. client.maxbody   = 1048576 * 100
  225. client.bufsize   = 2048
  226. client.timeout   = 60
  227.  
  228. function client:new(sock, callback)
  229.     self = setmetatable({}, self)
  230.     self.ip, self.port = sock:getpeername()
  231.     self.peer = self.ip .. ':' .. self.port
  232.     self.sock = sock
  233.     self.timer    = self.timeout
  234.     self.state    = 'waiting'
  235.     self.request  = {}
  236.     self.response = {}
  237.    
  238.     self:log('Connected')
  239.  
  240.     self.update = routine(self.update)
  241.     self:update(callback)
  242.     return self
  243. end
  244.  
  245. function client:config(conf)
  246.     for k, v in pairs(conf) do
  247.         self[k] = v
  248.     end
  249. end
  250.  
  251. function client:log(msg, level)
  252.     level = level or 'INFO'
  253.     print(self .. ' ' .. level .. ': ' .. tostring(msg))
  254. end
  255.  
  256. function client:_defaults(request, response)
  257.     request.verb     = 'GET'
  258.     request.headers  = {}
  259.     request.body     = ltn.sink.string:new()
  260.     response.code    = 200
  261.     response.headers = {}
  262.     response.body    = ''
  263. end
  264.  
  265. -- async code looks like sync
  266. function client:update(callback)
  267.     while true do
  268.         local request    = self.request
  269.         local response   = self.response
  270.        
  271.         self:_defaults(request, response)
  272.        
  273.         self.state = 'receive-headers'
  274.        
  275.         local tail, err = self:receiveHeaders()
  276.         if not tail then
  277.             self:log(err, 'ERROR')
  278.             return self:shutdown(true)
  279.         end
  280.        
  281.         local succ = true
  282.         if request.headers['content-length'] then
  283.             self.state = 'receive-body'
  284.             succ, err = self:receiveBody(tail)
  285.         elseif request.headers['transfer-encoding'] == 'chunked' then
  286.             self.state = 'receive-body'
  287.             succ, err = self:receiveChunkedBody(tail)
  288.         end
  289.  
  290.         if not succ then
  291.             self:log(err, 'ERROR')
  292.             return self:shutdown(true)
  293.         end
  294.        
  295.         if type(callback) == 'function' then
  296.             request.text = tostring(request.body)
  297.              -- userfunc
  298.             self.state = 'call-userfunction'
  299.             local succ, err = xpcall(callback, debug.traceback, self)
  300.             if not succ then
  301.                 self:log(self .. ' ' .. tostring(err), 'ERROR')
  302.             end
  303.         end
  304.        
  305.         local close = self:createHeaders()
  306.         self.state = 'send-headers'
  307.         self:sendHeaders()
  308.         self.state = 'send-body'
  309.         self:sendBody()
  310.  
  311.         if close then
  312.             self:shutdown()
  313.         end
  314.         coroutine.yield()
  315.     end
  316. end
  317.  
  318. function client:shutdown(send_response)
  319.     if send_response and self.sock then
  320.         self:createHeaders()
  321.         self.response.headers['Connection'] = 'Close'
  322.        
  323.         self:sendHeaders()
  324.         self:sendBody()
  325.         if self.request.body then
  326.             self.request.body('', true) -- close file handles
  327.         end
  328.         if self.request.filename then
  329.             os.remove(self.request.filename)
  330.         end
  331.     end
  332.     self:log('Shutdown')
  333.     self.sock:close()
  334.     self.sock = nil
  335.     coroutine.yield(true)
  336. end
  337.  
  338. function client:resetTimeout()
  339.     self.timer = self.timeout
  340. end
  341.  
  342. function client:receiveHeaders()
  343.     local request = self.request
  344.    
  345.     local headers = ''
  346.    
  347.     local i = 0
  348.     while true do
  349.         i = i + 1
  350.         local chunk, status, partial = self.sock:receive(self.bufsize)
  351.         chunk = chunk or partial
  352.        
  353.         if chunk and chunk ~= '' then
  354.             self:resetTimeout()
  355.            
  356.             local head, tail = chunk:match('(.-\r?\n)\r?\n(.*)')
  357.             if head then
  358.                 headers = headers .. head
  359.                 self:parseHeaders(headers)
  360.                 return tail -- part of body
  361.             else
  362.                 headers = headers .. chunk
  363.                 coroutine.yield()
  364.             end
  365.            
  366.             if #chunk > self.maxheader then
  367.                 self.response.code = 431
  368.                 return nil, 'Too large header'
  369.             end
  370.         else
  371.             coroutine.yield()
  372.         end
  373.     end
  374.     return true
  375. end
  376.  
  377. function client:parseHeaders(headers_str)
  378.     local request = self.request
  379.  
  380.     local firstline, headers_str = headers_str:match('(.-)\r?\n(.*)')
  381.     if not firstline then
  382.         self.response.code = 400
  383.         return nil, 'Bad request (first line not found)'
  384.     end
  385.    
  386.     request.verb, request.uri, self.proto = firstline:match('^(.-)%s([^%s]+)%s?(.*)$')
  387.     if not request.verb then
  388.         self.response.code = 400
  389.         return nil, 'Bad request (bad first line)'
  390.     end
  391.  
  392.     local headers = request.headers
  393.     for line in headers_str:gmatch('(.-)\r?\n') do
  394.         local key, value = line:match('(.-): (.*)')
  395.         if key then
  396.             local numvalue = value:match('^%-?%x+$')
  397.             numvalue = numvalue and tonumber(numvalue)
  398.             headers[key:lower()] = numvalue or value
  399.         end
  400.     end
  401. end
  402.  
  403. function client:_gettempfile(host)
  404.     local temp = 'bodydata_' .. host .. ' '
  405.     local i = 0
  406.     while not touch(temp .. i .. '.tmp') do
  407.         i = i + 1
  408.     end
  409.    
  410.     temp = temp .. i .. '.tmp'
  411.     return temp, io.open(temp, 'wb')
  412. end
  413.  
  414. function client:receiveBody(chunk)
  415.     local request = self.request
  416.     local len = request.headers['content-length'] or 0
  417.    
  418.     if len > self.maxbody then
  419.         local f = formatnum
  420.         self.response.code = 413
  421.         return nil, ('Too large body: %s (max: %s)'):format(f(len), f(self.maxbody))
  422.     end
  423.  
  424.     if len > self.filebody then
  425.         local name, file = self:_gettempfile()
  426.         self:log('Large body (' .. formatnum(len) .. '), create new temp file ' .. name)
  427.         self.filename = name
  428.         self.body     = ltn.sink.file(file)
  429.     end
  430.    
  431.     local chunk, status, partial = chunk
  432.     while true do
  433.         if chunk then
  434.             request.body(chunk)
  435.             self:resetTimeout()
  436.         end
  437.        
  438.         chunk, status, partial = self.sock:receive(self.bufsize)
  439.         chunk = chunk or partial
  440.  
  441.         local rcvlen = request.body:len()
  442.         if rcvlen == len then
  443.             request.body('', true) -- close
  444.             return true
  445.         end
  446.        
  447.         coroutine.yield()
  448.     end
  449. end
  450.  
  451. function client:receiveChunkedBody(chunk)
  452.     local request = self.request
  453.     local rexp = '^[\r\n]*(%x+)\r?\n(.*)'
  454.    
  455.     local len, chunk = chunk:match(rexp)
  456.     len = tonumber(len, 16)
  457.  
  458.     while true and len > 0 do
  459.         local data, status, partial = self.sock:receive(self.bufsize)
  460.         data = data or partial
  461.        
  462.         if data and data ~= '' then
  463.             chunk = chunk .. data
  464.         end
  465.        
  466.         while #chunk >= len and len > 0 do
  467.             local part = chunk:sub(1, len)
  468.             chunk = chunk:sub(len + 1)
  469.             len = chunk:match(rexp)
  470.             len = tonumber(len, 16)
  471.             request.body(part)
  472.             self:resetTimeout()
  473.         end
  474.         coroutine.yield()
  475.     end
  476.     request.body('', true) -- close
  477.     return true
  478. end
  479.  
  480. function client:sendHeaders()
  481.     local str, insert = tostring, table.insert
  482.    
  483.     local response = self.response
  484.    
  485.     local ccr = Codes[response.code] or 'OK'
  486.    
  487.     local template = {
  488.         self.proto .. ' ' .. response.code .. ' ' .. ccr
  489.     }
  490.    
  491.     for k, v in pairs(response.headers) do
  492.         insert(template, str(k) .. ': ' .. str(v))
  493.     end
  494.     insert(template, '\r\n')
  495.    
  496.     local headers = table.concat(template, '\r\n')
  497.    
  498.     self.sock:send(headers)
  499. end
  500.  
  501. function client:sendBody()
  502.     if not self.response.body then return end
  503.     local source = self.response.body
  504.     local chunk  = source()
  505.    
  506.     while true do
  507.         for i = 1, 6 do
  508.             if not chunk then return end
  509.             self.sock:send(chunk)
  510.             chunk = source()
  511.             self:resetTimeout()
  512.         end
  513.         coroutine.yield()
  514.     end
  515. end
  516.  
  517. function client:__tostring()
  518.     return 'Client: ' .. self.peer
  519. end
  520.  
  521. function client:__concat(other)
  522.     return tostring(self) .. tostring(other)
  523. end
  524.  
  525. function client:createHeaders()
  526.     local response = self.response
  527.     local headers  = response.headers
  528.  
  529.     for k, v in pairs(headers) do
  530.         local canonic    = Headers[k] or k
  531.         headers[k]       = nil
  532.         headers[canonic] = v
  533.     end
  534.    
  535.     local len = 0
  536.     if response.body then
  537.         local body = response.body
  538.         if type(body) == 'table' and body.len then -- already source
  539.             len = body:len()
  540.         else
  541.             local type = type(body)
  542.             if type == 'userdata' and body.read and body.write and body.close then
  543.                 type = 'file'
  544.             end
  545.            
  546.             local source  = assert(ltn.source[type], 'Unknown body type: ' .. type)
  547.             response.body = source:new(response.body)
  548.             len = response.body:len()
  549.         end
  550.     end
  551.     headers['Content-Length'] = len
  552.    
  553.     local request_headers = self.request.headers
  554.     headers['Connection'] = request_headers['Connection'] or 'keep-alive'
  555.    
  556.     if headers['Connection']:lower() == 'close' then
  557.         return true
  558.     end
  559. end
  560.  
  561. function client:updateTime(dt)
  562.     self.timer = self.timer - dt
  563.     if self.timer < 0 then
  564.         return true, 'Timeout'
  565.     end
  566. end
  567.  
  568. local server = {}
  569. server.__index = server
  570. server.host = '*'
  571. server.port = 80
  572.  
  573. function server:new(host, port, callback)
  574.     self = setmetatable({}, self)
  575.     self.sock = (socket.tcp4 or socket.tcp)()
  576.     self.time = socket.gettime()
  577.     self.host = host
  578.     self.port = port
  579.     self.clients = {}
  580.    
  581.     self.sock:bind(self.host, self.port)
  582.     self.sock:settimeout(0)
  583.    
  584.     self.sock:listen()
  585.    
  586.     return self
  587. end
  588.  
  589. function server:getdelta()
  590.     local time = socket.gettime()
  591.     local dt = self.time - time
  592.     self.time = time
  593.     return dt
  594. end
  595.  
  596. function server:update()
  597.     local dt = self:getdelta()
  598.    
  599.     local sock = self.sock:accept()
  600.     while sock do
  601.         sock:settimeout(0)
  602.         self.clients[sock] = client:new(sock, self.callback)
  603.         sock = self.sock:accept()
  604.     end
  605.    
  606.     for sock, cli in pairs(self.clients) do
  607.         if cli:updateTime(dt) or cli:update() then
  608.             cli:shutdown()
  609.             self.clients[sock] = nil
  610.         end
  611.     end
  612. end
  613.  
  614. if ... then
  615.     return server
  616. end
  617.  
  618. local s = server:new()
  619. function server:callback()
  620.     --print('URI [' .. self.request.uri .. ']')
  621.     --print('RECEIVED HEADERS:')
  622.     for k, v in pairs(self.request.headers) do
  623.         --print(k, v)
  624.     end
  625.     --print('RECEIVED BODY: ', self.request.body)
  626.     local f = io.open('C:\\Users\\Marceline\\TEMPFILE.zip', 'wb')
  627.     f:write(self.request.text); f:close()
  628.     if self.request.uri == '/favicon.ico' then
  629.         --print('SEND ICON')
  630.         self.response.headers['content-type'] = 'image/x-icon'
  631.         self.response.body = io.open('C:\\Users\\Marceline\\Desktop\\favicon.ico', 'rb')
  632.     else
  633.         self.response.body = 'Hai!'
  634.     end
  635. end
  636.  
  637. while true do
  638.     s:update()
  639.     socket.sleep(.001)
  640. end
Advertisement
Add Comment
Please, Sign In to add comment