Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local socket = require 'socket'
- local config = {
- port = 8080,
- timeout = 30,
- chunklimit = 10,
- chunksize = 1024,
- body_limit = 1024 * 1024 * 5,
- }
- -- ######## Utility block
- local function appendfile(path, text)
- local file = assert(io.open(path), 'a+')
- file:write(tostring(text))
- file:close()
- end
- -- ######## Client block
- local client = {}
- client.__index = client
- function client:new(sock, server)
- self = setmetatable({}, self)
- self.sock = sock
- self.server = server
- self.close_t = server.config.timeout
- self.timeout = server.config.timeout
- self.onMessage = server.onMessage
- self.header = {}
- self.__body = {}
- self.__receive = 0
- self.loggingLayout = 'Client ' .. self.sock:getpeername() .. ' '
- self.sock:settimeout(0)
- return self
- end
- -- Пользуемся серверным логом.
- function client:log(msg)
- self.server:log(self.loggingLayout .. tostring(msg))
- end
- function client:debug(msg)
- self.server:debug(self.loggingLayout .. tostring(msg))
- end
- local function trace(msg, layer)
- return debug.traceback("Error: " .. tostring(msg), 1 + (layer or 1) )
- end
- function client:callback()
- -- эта штука генерирует функцию do-end на каждый запрос, по хорошему - выделить её отдельно.
- -- Трейсбек <3
- local succ, err = xpcall(function() self:onMessage() end, trace)
- if not succ then
- err = tostring(err)
- self:send(err, '500')
- self:log('Client: error: ' .. err)
- end
- end
- -- Тут есть самоубийство на случай дисконнекта!
- function client:atomReceive(mode)
- local data, status, partial = self.sock:receive(mode)
- if status == 'closed' then
- self:destroy()
- return
- end
- return data, status
- end
- function client:receive()
- if self.destroyed then return end
- -- Этот финт позволяет не напрягаясь параллельно принимать заголовки входящих запросов
- local data, status = self:atomReceive('*l')
- while data do
- table.insert(self.header, data)
- if data == '' then
- self:parseHeaders()
- if (self.verb == 'POST' or self.verb == 'PUT') and self.contentLength then
- if self.contentLength > config.body_limit then
- self:send(
- 'Body is too big: limit is '
- .. config.body_limit .. ', got: '
- .. self.contentLength,
- '400'
- )
- return self:destroy()
- end
- self.__receive = self.contentLength
- self.receive = self.receiveBody
- return
- end
- self:callback()
- self:reset()
- end
- data, status = self:atomReceive('*l')
- end
- end
- function client:receiveBody()
- if self.destroyed then return end
- local chunksize = config.chunksize
- local chunks = 0
- local data, status = self:atomReceive(math.min(chunksize, self.__receive))
- while data do
- chunks = chunks + 1
- self.__receive = self.__receive - #data
- table.insert(self.__body, data)
- if self.__receive == 0 then
- self.data = table.concat(self.__body)
- self:callback()
- self:reset()
- end
- data, status = self:atomReceive(math.min(chunksize, self.__receive))
- end
- if self.connectionClose then
- self:destroy()
- end
- end
- function client:reset()
- self.receive = nil
- self.__receive = 0
- self.__body = {}
- self.header = {}
- end
- function client:parseHeaders()
- local head = self.header[1] or 'UNKNOWN'
- self.verb = head:match('%w+')
- -- Строчные запросы
- if head:find('?') then
- local querry = head:match('?(.-) ') .. '&'
- local data = {}
- for key, value in querry:gmatch('(.-)=(.-)&') do
- data[key] = value
- end
- self.querry = data
- end
- -- Остальные заголовки
- for i = 2, #self.header do
- local header, data = self.header[i]:match('(.-): (.*)')
- if header then
- self.header[header:lower()] = data
- end
- end
- -- Числа стоит преобразовать в числа
- if self.header['content-length'] then
- self.contentLength = tonumber(self.header['content-length'])
- self.connectionClose = (self.header['connection'] or ''):lower() == 'close'
- end
- end
- -- Клиент сам убивает себя и удаляет у сервера
- function client:destroy()
- if self.destroyed then return end
- self.destroyed = true
- self:close()
- -- если где-то ошибка с удалением - лучше перестраховаться
- return self.server:removeClient(self.sock) or self.server:removeAllClients()
- end
- function client:update(dt)
- self.timeout = self.timeout - dt
- if self.timeout < 0 then return self:destroy() end
- if self:receive() then return self end
- end
- function client:send(msg, status)
- status = status or self.header['status'] or '200'
- local headers = {}
- headers['content-length'] = #msg
- -- Ответ как бы в того же типа что и запрос, но можно поменять в процессе
- headers['content-type'] = self.header['content-type'] or 'text/html'
- headers['server'] = 'luasocket-server'
- -- Достаточно написать '200' или '500' или '404' чтобы был достаточно валидный статус, лишних строк не нужно.
- -- Его напишет и в первую строку ответа и в хедер статуса, норм
- headers['status'] = status:match('%d+')
- headers['connection'] = 'close'
- local output = {}
- output[1] = 'HTTP/1.1 ' .. status
- for k, v in pairs(headers) do
- table.insert(output, k .. ': ' .. v)
- end
- table.insert(output, '')
- table.insert(output, msg)
- output = table.concat(output, '\r\n')
- self:debug('Client send: \n['..output..']')
- self.sock:send(output)
- end
- -- закрытие без уничтожения, хм
- function client:close()
- self.sock:close()
- end
- -- ######## Server block
- server = {}
- server.__index = server
- -- Имя нужно для логирования и некоторых специальных вещей (типа автозабора свойств из конфига).
- -- Колбек и порт можно указать позже, почти в любой момент.
- function server:new(name, plog, port, on_message)
- name = assert(type(name) == 'string' and name, 'Arg#1 error: string server-name expected, got '..type(name))
- plog = assert(type(plog) == 'string' and plog, 'Arg#2 error: string logging path expected, got '..type(plog))
- self = setmetatable({}, self)
- self.name = name
- self.config = {}
- self.clients = {}
- self.clientCount = 0
- self.time = socket.gettime()
- self.config.port = port or config.port
- self.config.timeout = config.timeout
- self.onMessage = on_message
- self.textblock = {}
- self.loggingPath = plog .. '/http_server_'..self.name..'.log'
- self:log('Init')
- self.dbg = true
- return self
- end
- function server:debug(mode)
- self.dbg = not not mode
- end
- function server:log(msg)
- appendfile(self.loggingPath, os.date('%c', os.time()) .. ' LOG ' ..tostring(msg)..'\r\n')
- end
- function server:debug(msg)
- if not self.dbg then return end
- appendfile(self.loggingPath, os.date('%c', os.time()) .. ' DEBUG ' ..tostring(msg)..'\r\n')
- end
- setmetatable(server, {__call = server.new})
- -- Обновление таймеров клиентов, чтобы отрубались когда нужно.
- function server:getDeltaTime()
- local tm = socket.gettime()
- local dt = tm - self.time
- self.time = tm
- return dt
- end
- function server:setTimeout(n)
- self.config.timeout = assert(type(n) == 'number' and n > 0 and n, 'arg#1 error: positive number expected, got ' .. type(n) .. ': ' .. tostring(n))
- self:log('Set timeout: ' .. n)
- return self
- end
- -- Ну не будет же клиент сам ломиться в сервак и удалять себя.
- function server:removeClient(sock)
- self:debug('Server: remove client: '..tostring(sock:getpeername()))
- local succ = self.clients[sock] ~= nil
- self.clients[sock] = nil
- return succ
- end
- -- Экстренная очистка
- function server:removeAllClients()
- self:log('Server: remove all clients')
- for k, v in pairs(self.clients) do
- v:close()
- k:close()
- end
- self.clients = {}
- end
- function server:setCallback(cb)
- self.onMessage = assert(type(cb) == 'function' and cb or 'arg#1 error: callback function expected, got '..type(cb))
- self:log('Server: set callback '..tostring(cb))
- return self
- end
- function server:start(port, cb)
- self.onMessage = cb or self.onMessage
- self.onMessage = assert(type(self.onMessage) == 'function' and self.onMessage, 'Set callback function first!')
- self.config.port = port or self.config.port
- local channel, err = socket.bind('*', self.config.port)
- if not channel then
- return nil, err
- end
- self:log('Server: start: '..tostring(self.config.port) .. ': ' .. tostring(channel) .. ' error?:' ..tostring(err))
- self.channel = assert(channel, err)
- self.channel:settimeout(0)
- return self
- end
- function server:shutdown()
- self:removeAllClients()
- self.channel:close()
- self:log('Server: shutdown')
- end
- function server:accept()
- local sock = self.channel:accept()
- while sock do
- self.clients[sock] = client:new(sock, self)
- self:debug('Server: new client '..tostring(sock:getpeername()) )
- sock = self.channel:accept()
- end
- end
- function server:update(dt)
- self.clientCount = 0
- for sock, client in pairs(self.clients) do
- self.clientCount = self.clientCount + 1
- client:update(dt)
- end
- self.textblock['Listen'] = self.config.port
- local cliCount, cliReceive = 0, 0
- for k, v in pairs(self.clients) do
- cliCount = cliCount + 1
- cliReceive = cliReceive + v.__receive
- end
- self.textblock['Clients'] = cliCount
- self.textblock['Receive'] = cliReceive
- self.textblock['Time'] = math.floor(self.time)
- end
- function server:loop()
- while true do
- local dt = self:getDeltaTime()
- self:accept()
- self:update(dt)
- socket.sleep(.02)
- end
- end
- if not ... then
- local http = server:new('Simple_test', '*')
- local function onMessage(cli)
- if cli.header['content-length'] then
- cli:send(cli.data, '200')
- return
- end
- cli:send('Yo', '200')
- end
- http:start(8080, onMessage)
- http:loop()
- end
- return server
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement