Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local socket = require'socket'
- -- cds - canonic code reason
- local ltn, uri
- if ... then
- ltn = require'sock.ltn'
- uri = require'sock.uri'
- else -- testing
- ltn = require'ltn'
- uri = require'uri'
- end
- local function touch(filepath)
- local file = io.open(filepath, 'rb')
- if not file then
- return false
- end
- return true, file:close()
- end
- local function formatnum(v)
- v = tonumber(v) or 0
- v = tostring(v)
- return v:reverse():gsub('%d%d%d', '%1\''):reverse():match('%d.*')
- end
- local function routine(func)
- return coroutine.wrap(function(...)
- local res = {xpcall(func, debug.traceback, ...)}
- if res[1] then
- table.remove(res, 1)
- return unpack(res)
- end
- error(res[2], 2)
- end)
- end
- local Headers = {
- ["accept"] = "Accept",
- ["accept-charset"] = "Accept-Charset",
- ["accept-encoding"] = "Accept-Encoding",
- ["accept-language"] = "Accept-Language",
- ["accept-ranges"] = "Accept-Ranges",
- ["action"] = "Action",
- ["alternate-recipient"] = "Alternate-Recipient",
- ["age"] = "Age",
- ["allow"] = "Allow",
- ["arrival-date"] = "Arrival-Date",
- ["authorization"] = "Authorization",
- ["bcc"] = "Bcc",
- ["cache-control"] = "Cache-Control",
- ["cc"] = "Cc",
- ["comments"] = "Comments",
- ["connection"] = "Connection",
- ["content-description"] = "Content-Description",
- ["content-disposition"] = "Content-Disposition",
- ["content-encoding"] = "Content-Encoding",
- ["content-id"] = "Content-ID",
- ["content-language"] = "Content-Language",
- ["content-length"] = "Content-Length",
- ["content-location"] = "Content-Location",
- ["content-md5"] = "Content-MD5",
- ["content-range"] = "Content-Range",
- ["content-transfer-encoding"] = "Content-Transfer-Encoding",
- ["content-type"] = "Content-Type",
- ["cookie"] = "Cookie",
- ["date"] = "Date",
- ["diagnostic-code"] = "Diagnostic-Code",
- ["dsn-gateway"] = "DSN-Gateway",
- ["etag"] = "ETag",
- ["expect"] = "Expect",
- ["expires"] = "Expires",
- ["final-log-id"] = "Final-Log-ID",
- ["final-recipient"] = "Final-Recipient",
- ["from"] = "From",
- ["host"] = "Host",
- ["if-match"] = "If-Match",
- ["if-modified-since"] = "If-Modified-Since",
- ["if-none-match"] = "If-None-Match",
- ["if-range"] = "If-Range",
- ["if-unmodified-since"] = "If-Unmodified-Since",
- ["in-reply-to"] = "In-Reply-To",
- ["keywords"] = "Keywords",
- ["last-attempt-date"] = "Last-Attempt-Date",
- ["last-modified"] = "Last-Modified",
- ["location"] = "Location",
- ["max-forwards"] = "Max-Forwards",
- ["message-id"] = "Message-ID",
- ["mime-version"] = "MIME-Version",
- ["original-envelope-id"] = "Original-Envelope-ID",
- ["original-recipient"] = "Original-Recipient",
- ["pragma"] = "Pragma",
- ["proxy-authenticate"] = "Proxy-Authenticate",
- ["proxy-authorization"] = "Proxy-Authorization",
- ["range"] = "Range",
- ["received"] = "Received",
- ["received-from-mta"] = "Received-From-MTA",
- ["references"] = "References",
- ["referer"] = "Referer",
- ["remote-mta"] = "Remote-MTA",
- ["reply-to"] = "Reply-To",
- ["reporting-mta"] = "Reporting-MTA",
- ["resent-bcc"] = "Resent-Bcc",
- ["resent-cc"] = "Resent-Cc",
- ["resent-date"] = "Resent-Date",
- ["resent-from"] = "Resent-From",
- ["resent-message-id"] = "Resent-Message-ID",
- ["resent-reply-to"] = "Resent-Reply-To",
- ["resent-sender"] = "Resent-Sender",
- ["resent-to"] = "Resent-To",
- ["retry-after"] = "Retry-After",
- ["return-path"] = "Return-Path",
- ["sender"] = "Sender",
- ["server"] = "Server",
- ["smtp-remote-recipient"] = "SMTP-Remote-Recipient",
- ["status"] = "Status",
- ["subject"] = "Subject",
- ["te"] = "TE",
- ["to"] = "To",
- ["trailer"] = "Trailer",
- ["transfer-encoding"] = "Transfer-Encoding",
- ["upgrade"] = "Upgrade",
- ["user-agent"] = "User-Agent",
- ["vary"] = "Vary",
- ["via"] = "Via",
- ["warning"] = "Warning",
- ["will-retry-until"] = "Will-Retry-Until",
- ["www-authenticate"] = "WWW-Authenticate",
- ["x-mailer"] = "X-Mailer",
- }
- local Codes = {
- -- INFO
- [100] = 'Continue',
- [101] = 'Switchibg Protocols',
- [102] = 'Processing',
- -- SUCCESS
- [200] = 'OK',
- [201] = 'Created',
- [202] = 'Accepted',
- [203] = 'Non-Authoritative Information',
- [204] = 'No Content',
- [205] = 'Reset Content',
- [206] = 'Partial Content',
- [207] = 'Multi-Status',
- [208] = 'Already Reported',
- [226] = 'IM Used',
- -- REDIRECT
- [300] = 'Multiple Choices',
- [301] = 'Moved Permanently',
- [302] = 'Moved Temporarily',
- [302] = 'Found', -- hehe
- [303] = 'See Other',
- [304] = 'Not Modified',
- [305] = 'Use Proxy',
- [306] = 'Reserved',
- [307] = 'Temporary Redirect',
- [308] = 'Permanent Redirect',
- -- REQUEST ERROR
- [400] = 'Bad Request',
- [401] = 'Unauthorized',
- [402] = 'Payment Required',
- [403] = 'Forbidden',
- [404] = 'Not Found',
- [405] = 'Method Not Allowed',
- [406] = 'Not Acceptable',
- [407] = 'Proxy Authentication Required',
- [408] = 'Request Timeout',
- [409] = 'Conflict',
- [410] = 'Gone',
- [411] = 'Length Required',
- [412] = 'Precondition Failed',
- [413] = 'Payload Too Large',
- [414] = 'URI Too Long',
- [415] = 'Unsupported Media Type',
- [416] = 'Range Not Satisfiable',
- [417] = 'Expectation Failed',
- [418] = 'I\'m a teapot',
- [419] = 'Authentication Timeout',
- [421] = 'Misdirected Request',
- [422] = 'Unprocessable Entity',
- [423] = 'Locked ',
- [424] = 'Failed Dependency',
- [426] = 'Upgrade Required',
- [428] = 'Precondition Required',
- [429] = 'Too Many Requests',
- [431] = 'Request Header Fields Too Large',
- [434] = 'Requested host unavailable',
- [449] = 'Retry With',
- [451] = 'Unavailable For Legal Reasons',
- [499] = 'Client Closed Request',
- -- SERVER ERROR
- [500] = 'Internal Server Error',
- [501] = 'Not Implemented',
- [502] = 'Bad Gateway',
- [503] = 'Service Unavailable',
- [504] = 'Gateway Timeout',
- [505] = 'HTTP Version Not Supported',
- [506] = 'Variant Also Negotiates',
- [507] = 'Insufficient Storage',
- [509] = 'Bandwidth Limit Exceeded',
- [510] = 'Not Extended',
- [511] = 'Network Authentication Required',
- [521] = 'Web Server Is Down',
- [522] = 'Connection Timed Out',
- [523] = 'Origin Is Unreachable',
- [525] = 'SSL Handshake Failed',
- [526] = 'Invalid SSL Certificate'
- }
- local client = {}
- client.__index = client
- client.timeout = 60
- client.maxheader = 1048576 * 0.5 -- 0.5mb
- client.fileroot = os.getenv('')
- client.filebody = 1048576 * 5
- client.maxbody = 1048576 * 100
- client.bufsize = 2048
- client.timeout = 60
- function client:new(sock, callback)
- self = setmetatable({}, self)
- self.ip, self.port = sock:getpeername()
- self.peer = self.ip .. ':' .. self.port
- self.sock = sock
- self.timer = self.timeout
- self.state = 'waiting'
- self.request = {}
- self.response = {}
- self:log('Connected')
- self.update = routine(self.update)
- self:update(callback)
- return self
- end
- function client:config(conf)
- for k, v in pairs(conf) do
- self[k] = v
- end
- end
- function client:log(msg, level)
- level = level or 'INFO'
- print(self .. ' ' .. level .. ': ' .. tostring(msg))
- end
- function client:_defaults(request, response)
- request.verb = 'GET'
- request.headers = {}
- request.body = ltn.sink.string:new()
- response.code = 200
- response.headers = {}
- response.body = ''
- end
- -- async code looks like sync
- function client:update(callback)
- while true do
- local request = self.request
- local response = self.response
- self:_defaults(request, response)
- self.state = 'receive-headers'
- local tail, err = self:receiveHeaders()
- if not tail then
- self:log(err, 'ERROR')
- return self:shutdown(true)
- end
- local succ = true
- if request.headers['content-length'] then
- self.state = 'receive-body'
- succ, err = self:receiveBody(tail)
- elseif request.headers['transfer-encoding'] == 'chunked' then
- self.state = 'receive-body'
- succ, err = self:receiveChunkedBody(tail)
- end
- if not succ then
- self:log(err, 'ERROR')
- return self:shutdown(true)
- end
- if type(callback) == 'function' then
- request.text = tostring(request.body)
- -- userfunc
- self.state = 'call-userfunction'
- local succ, err = xpcall(callback, debug.traceback, self)
- if not succ then
- self:log(self .. ' ' .. tostring(err), 'ERROR')
- end
- end
- local close = self:createHeaders()
- self.state = 'send-headers'
- self:sendHeaders()
- self.state = 'send-body'
- self:sendBody()
- if close then
- self:shutdown()
- end
- coroutine.yield()
- end
- end
- function client:shutdown(send_response)
- if send_response and self.sock then
- self:createHeaders()
- self.response.headers['Connection'] = 'Close'
- self:sendHeaders()
- self:sendBody()
- if self.request.body then
- self.request.body('', true) -- close file handles
- end
- if self.request.filename then
- os.remove(self.request.filename)
- end
- end
- self:log('Shutdown')
- self.sock:close()
- self.sock = nil
- coroutine.yield(true)
- end
- function client:resetTimeout()
- self.timer = self.timeout
- end
- function client:receiveHeaders()
- local request = self.request
- local headers = ''
- local i = 0
- while true do
- i = i + 1
- local chunk, status, partial = self.sock:receive(self.bufsize)
- chunk = chunk or partial
- if chunk and chunk ~= '' then
- self:resetTimeout()
- local head, tail = chunk:match('(.-\r?\n)\r?\n(.*)')
- if head then
- headers = headers .. head
- self:parseHeaders(headers)
- return tail -- part of body
- else
- headers = headers .. chunk
- coroutine.yield()
- end
- if #chunk > self.maxheader then
- self.response.code = 431
- return nil, 'Too large header'
- end
- else
- coroutine.yield()
- end
- end
- return true
- end
- function client:parseHeaders(headers_str)
- local request = self.request
- local firstline, headers_str = headers_str:match('(.-)\r?\n(.*)')
- if not firstline then
- self.response.code = 400
- return nil, 'Bad request (first line not found)'
- end
- request.verb, request.uri, self.proto = firstline:match('^(.-)%s([^%s]+)%s?(.*)$')
- if not request.verb then
- self.response.code = 400
- return nil, 'Bad request (bad first line)'
- end
- local headers = request.headers
- for line in headers_str:gmatch('(.-)\r?\n') do
- local key, value = line:match('(.-): (.*)')
- if key then
- local numvalue = value:match('^%-?%x+$')
- numvalue = numvalue and tonumber(numvalue)
- headers[key:lower()] = numvalue or value
- end
- end
- end
- function client:_gettempfile(host)
- local temp = 'bodydata_' .. host .. ' '
- local i = 0
- while not touch(temp .. i .. '.tmp') do
- i = i + 1
- end
- temp = temp .. i .. '.tmp'
- return temp, io.open(temp, 'wb')
- end
- function client:receiveBody(chunk)
- local request = self.request
- local len = request.headers['content-length'] or 0
- if len > self.maxbody then
- local f = formatnum
- self.response.code = 413
- return nil, ('Too large body: %s (max: %s)'):format(f(len), f(self.maxbody))
- end
- if len > self.filebody then
- local name, file = self:_gettempfile()
- self:log('Large body (' .. formatnum(len) .. '), create new temp file ' .. name)
- self.filename = name
- self.body = ltn.sink.file(file)
- end
- local chunk, status, partial = chunk
- while true do
- if chunk then
- request.body(chunk)
- self:resetTimeout()
- end
- chunk, status, partial = self.sock:receive(self.bufsize)
- chunk = chunk or partial
- local rcvlen = request.body:len()
- if rcvlen == len then
- request.body('', true) -- close
- return true
- end
- coroutine.yield()
- end
- end
- function client:receiveChunkedBody(chunk)
- local request = self.request
- local rexp = '^[\r\n]*(%x+)\r?\n(.*)'
- local len, chunk = chunk:match(rexp)
- len = tonumber(len, 16)
- while true and len > 0 do
- local data, status, partial = self.sock:receive(self.bufsize)
- data = data or partial
- if data and data ~= '' then
- chunk = chunk .. data
- end
- while #chunk >= len and len > 0 do
- local part = chunk:sub(1, len)
- chunk = chunk:sub(len + 1)
- len = chunk:match(rexp)
- len = tonumber(len, 16)
- request.body(part)
- self:resetTimeout()
- end
- coroutine.yield()
- end
- request.body('', true) -- close
- return true
- end
- function client:sendHeaders()
- local str, insert = tostring, table.insert
- local response = self.response
- local ccr = Codes[response.code] or 'OK'
- local template = {
- self.proto .. ' ' .. response.code .. ' ' .. ccr
- }
- for k, v in pairs(response.headers) do
- insert(template, str(k) .. ': ' .. str(v))
- end
- insert(template, '\r\n')
- local headers = table.concat(template, '\r\n')
- self.sock:send(headers)
- end
- function client:sendBody()
- if not self.response.body then return end
- local source = self.response.body
- local chunk = source()
- while true do
- for i = 1, 6 do
- if not chunk then return end
- self.sock:send(chunk)
- chunk = source()
- self:resetTimeout()
- end
- coroutine.yield()
- end
- end
- function client:__tostring()
- return 'Client: ' .. self.peer
- end
- function client:__concat(other)
- return tostring(self) .. tostring(other)
- end
- function client:createHeaders()
- local response = self.response
- local headers = response.headers
- for k, v in pairs(headers) do
- local canonic = Headers[k] or k
- headers[k] = nil
- headers[canonic] = v
- end
- local len = 0
- if response.body then
- local body = response.body
- if type(body) == 'table' and body.len then -- already source
- len = body:len()
- else
- local type = type(body)
- if type == 'userdata' and body.read and body.write and body.close then
- type = 'file'
- end
- local source = assert(ltn.source[type], 'Unknown body type: ' .. type)
- response.body = source:new(response.body)
- len = response.body:len()
- end
- end
- headers['Content-Length'] = len
- local request_headers = self.request.headers
- headers['Connection'] = request_headers['Connection'] or 'keep-alive'
- if headers['Connection']:lower() == 'close' then
- return true
- end
- end
- function client:updateTime(dt)
- self.timer = self.timer - dt
- if self.timer < 0 then
- return true, 'Timeout'
- end
- end
- local server = {}
- server.__index = server
- server.host = '*'
- server.port = 80
- function server:new(host, port, callback)
- self = setmetatable({}, self)
- self.sock = (socket.tcp4 or socket.tcp)()
- self.time = socket.gettime()
- self.host = host
- self.port = port
- self.clients = {}
- self.sock:bind(self.host, self.port)
- self.sock:settimeout(0)
- self.sock:listen()
- return self
- end
- function server:getdelta()
- local time = socket.gettime()
- local dt = self.time - time
- self.time = time
- return dt
- end
- function server:update()
- local dt = self:getdelta()
- local sock = self.sock:accept()
- while sock do
- sock:settimeout(0)
- self.clients[sock] = client:new(sock, self.callback)
- sock = self.sock:accept()
- end
- for sock, cli in pairs(self.clients) do
- if cli:updateTime(dt) or cli:update() then
- cli:shutdown()
- self.clients[sock] = nil
- end
- end
- end
- if ... then
- return server
- end
- local s = server:new()
- function server:callback()
- --print('URI [' .. self.request.uri .. ']')
- --print('RECEIVED HEADERS:')
- for k, v in pairs(self.request.headers) do
- --print(k, v)
- end
- --print('RECEIVED BODY: ', self.request.body)
- local f = io.open('C:\\Users\\Marceline\\TEMPFILE.zip', 'wb')
- f:write(self.request.text); f:close()
- if self.request.uri == '/favicon.ico' then
- --print('SEND ICON')
- self.response.headers['content-type'] = 'image/x-icon'
- self.response.body = io.open('C:\\Users\\Marceline\\Desktop\\favicon.ico', 'rb')
- else
- self.response.body = 'Hai!'
- end
- end
- while true do
- s:update()
- socket.sleep(.001)
- end
Advertisement
Add Comment
Please, Sign In to add comment