httpd don't need readline now

This commit is contained in:
Cloud Wu
2014-07-23 12:19:48 +08:00
parent 551d5048c4
commit 18f20425a0
6 changed files with 176 additions and 93 deletions

View File

@@ -23,7 +23,6 @@ struct buffer_node {
}; };
struct socket_buffer { struct socket_buffer {
int limit;
int size; int size;
int offset; int offset;
struct buffer_node *head; struct buffer_node *head;
@@ -66,7 +65,6 @@ lnewpool(lua_State *L, int sz) {
static int static int
lnewbuffer(lua_State *L) { lnewbuffer(lua_State *L) {
struct socket_buffer * sb = lua_newuserdata(L, sizeof(*sb)); struct socket_buffer * sb = lua_newuserdata(L, sizeof(*sb));
sb->limit = luaL_optint(L,1,BUFFER_LIMIT);
sb->size = 0; sb->size = 0;
sb->offset = 0; sb->offset = 0;
sb->head = NULL; sb->head = NULL;
@@ -129,9 +127,6 @@ lpushbuffer(lua_State *L) {
sb->size += sz; sb->size += sz;
lua_pushinteger(L, sb->size); lua_pushinteger(L, sb->size);
if (sb->limit > 0 && sb->size > sb->limit) {
return luaL_error(L, "buffer overflow (limit = %d, size = %d)", sb->limit, sb->size);
}
return 1; return 1;
} }

View File

@@ -1,7 +1,6 @@
local table = table local table = table
local httpd = {} local httpd = {}
local READLIMIT = 8192 -- limit bytes per read
local http_status_msg = { local http_status_msg = {
[100] = "Continue", [100] = "Continue",
@@ -46,76 +45,150 @@ local http_status_msg = {
[505] = "HTTP Version not supported", [505] = "HTTP Version not supported",
} }
local function recvheader(readline, header) local function recvheader(readbytes, limit, lines, last)
local line = readline() local idx = 1
if line == "" then local len = 0
return header local header = last
while true do
header = header .. readbytes()
if #header + len > limit then
return
end
while true do
local f,e = header:find("\r\n",1, true)
if f then
if f == 1 then
for i = idx, #lines do
lines[i] = nil
end
return header:sub(3)
end
lines[idx] = header:sub(1, f-1)
idx = idx + 1
len = len + f-1
header = header:sub(e+1)
else
break
end
end
end end
end
local function parseheader(lines, from, header)
local name, value local name, value
repeat for i=from,#lines do
local line = lines[i]
if line:byte(1) == 9 then -- tab, append last line if line:byte(1) == 9 then -- tab, append last line
if name == nil then
return
end
header[name] = header[name] .. line:sub(2) header[name] = header[name] .. line:sub(2)
else else
name, value = line:match "^(.-):%s*(.*)" name, value = line:match "^(.-):%s*(.*)"
assert(name and value) if name == nil or value == nil then
return
end
name = name:lower() name = name:lower()
if header[name] then if header[name] then
header[name] = header[name] .. ", " .. value header[name] = header[name] .. ", " .. value
else else
header[name] = value header[name] = value
end end
line = readline()
end end
until line == "" end
return header return header
end end
local function recvbody(readbytes, length) local function chunksize(readbytes, body)
if length < READLIMIT then
return readbytes(length)
end
local tmp = {}
while true do while true do
if length <= READLIMIT then if #body > 128 then
table.insert(tmp, readbytes(length)) return
end
body = body .. readbytes()
local f,e = body:find("\r\n",1,true)
if f then
return tonumber(body:sub(1,f-1),16), body:sub(e+1)
end
end
end
local function readcrln(readbytes, body)
if #body > 2 then
if body:sub(1,2) ~= "\r\n" then
return
end
return body:sub(3)
else
body = body .. readbytes(2-#body)
if body ~= "\r\n" then
return
end
return ""
end
end
local tmpline = {}
local function recvchunkedbody(readbytes, bodylimit, header, body)
local result = ""
local size = 0
while true do
local sz
sz , body = chunksize(readbytes, body)
if not sz then
return
end
if sz == 0 then
break break
end end
table.insert(tmp, readbytes(READLIMIT)) size = size + sz
length = length - READLIMIT if bodylimit and size > bodylimit then
return
end
if #body >= sz then
result = result .. body:sub(1,sz)
body = body:sub(sz+1)
else
result = result .. body .. readbytes(sz - #body)
body = ""
end
body = readcrln(readbytes, body)
if not body then
return
end
end end
return table.concat(tmp)
body = readcrln(readbytes, body)
if not body then
return
end
body = recvheader(readbytes, 8192, tmpline, body)
if not body then
return
end
header = parseheader(tmpline,1,header)
return result, header
end end
local function recvchunkedbody(readline, readbytes, header) local function readall(readbytes, bodylimit)
local size = assert(tonumber(readline(),16)) local body = recvheader(readbytes, 8192, tmpline, "")
local body = recvbody(readbytes,size) if not body then
assert(readbytes(2) == "\r\n") return 413 -- Request Entity Too Large
size = assert(tonumber(readline(),16))
if size > 0 then
local bodys = { body }
repeat
table.insert(bodys, recvbody(readbytes,size))
assert(readbytes(2) == "\r\n")
size = assert(tonumber(readline(),16))
until size <= 0
body = table.concat(bodys)
end end
assert(readbytes(2) == "\r\n") local request = assert(tmpline[1])
header = recvheader(readline, header)
return body, header
end
local function readall(readline, readbytes)
local request = readline()
local method, url, httpver = request:match "^(%a+)%s+(.-)%s+HTTP/([%d%.]+)$" local method, url, httpver = request:match "^(%a+)%s+(.-)%s+HTTP/([%d%.]+)$"
assert(method and url and httpver) assert(method and url and httpver)
httpver = assert(tonumber(httpver)) httpver = assert(tonumber(httpver))
if httpver < 1.0 or httpver > 1.1 then if httpver < 1.0 or httpver > 1.1 then
return 505 -- HTTP Version not supported return 505 -- HTTP Version not supported
end end
local header = recvheader(readline, {}) local header = parseheader(tmpline,2,{})
if not header then
return 400 -- Bad request
end
local length = header["content-length"] local length = header["content-length"]
if length then if length then
length = tonumber(length) length = tonumber(length)
@@ -127,23 +200,31 @@ local function readall(readline, readbytes)
end end
end end
local body
if mode == "chunked" then if mode == "chunked" then
body, header = recvchunkedbody(readline, readbytes, header) body, header = recvchunkedbody(readbytes, bodylimit, header, body)
if not body then
return 413
end
else else
-- identity mode -- identity mode
if length then if length then
body = readbody(readbytes, length) if length > bodylimit then
return 413
end
if #body >= length then
body = body:sub(1,length)
else
local padding = readbytes(length - #body)
body = body .. padding
end
end end
end end
return 200, url, method, header, body return 200, url, method, header, body
end end
function httpd.read_request(readfunc) function httpd.read_request(readbytes, bodylimit)
local readline = assert(readfunc.readline) local ok, code, url, method, header, body = pcall(readall, readbytes, bodylimit)
local readbytes = assert(readfunc.readbytes)
local ok, code, url, method, header, body = pcall(readall, readline, readbytes)
if ok then if ok then
return code, url, method, header, body return code, url, method, header, body
else else

View File

@@ -1,6 +1,5 @@
local socket = require "socket" local socket = require "socket"
local readline = socket.readline
local readbytes = socket.read local readbytes = socket.read
local writebytes = socket.write local writebytes = socket.write
@@ -10,18 +9,7 @@ local socket_error = {}
sockethelper.socket_error = socket_error sockethelper.socket_error = socket_error
function sockethelper.readfunc(fd) function sockethelper.readfunc(fd)
local helper = {} return function (sz)
function helper.readline()
local ret = readline(fd, "\r\n")
if ret then
return ret
else
error(socket_error)
end
end
function helper.readbytes(sz)
local ret = readbytes(fd, sz) local ret = readbytes(fd, sz)
if ret then if ret then
return ret return ret
@@ -29,8 +17,6 @@ function sockethelper.readfunc(fd)
error(socket_error) error(socket_error)
end end
end end
return helper
end end
function sockethelper.writefunc(fd) function sockethelper.writefunc(fd)

View File

@@ -47,14 +47,15 @@ local function write(fd, text)
end end
local function launch_slave(auth_handler) local function launch_slave(auth_handler)
-- set socket buffer limit (8K)
-- If the attacker send large package, close the socket
socket.limit(8192)
local function auth(fd, addr) local function auth(fd, addr)
fd = assert(tonumber(fd)) fd = assert(tonumber(fd))
skynet.error(string.format("connect from %s (fd = %d)", addr, fd)) skynet.error(string.format("connect from %s (fd = %d)", addr, fd))
socket.start(fd) socket.start(fd)
-- set socket buffer limit (8K)
-- If the attacker send large package, close the socket
socket.limit(fd, 8192)
local challenge = crypt.randomkey() local challenge = crypt.randomkey()
write(fd, crypt.base64encode(challenge).."\n") write(fd, crypt.base64encode(challenge).."\n")

View File

@@ -2,7 +2,6 @@ local driver = require "socketdriver"
local skynet = require "skynet" local skynet = require "skynet"
local assert = assert local assert = assert
local buffer_limit = -1
local socket = {} -- api local socket = {} -- api
local buffer_pool = {} -- store all message buffer object local buffer_pool = {} -- store all message buffer object
local socket_pool = setmetatable( -- store all socket object local socket_pool = setmetatable( -- store all socket object
@@ -48,13 +47,7 @@ socket_message[1] = function(id, size, data)
return return
end end
local ok , sz = pcall(driver.push, s.buffer, buffer_pool, data, size) local sz = driver.push(s.buffer, buffer_pool, data, size)
if not ok then
skynet.error("socket: error on ", id , sz)
driver.clear(s.buffer,buffer_pool)
driver.close(id)
return
end
local rr = s.read_required local rr = s.read_required
local rrt = type(rr) local rrt = type(rr)
if rrt == "number" then if rrt == "number" then
@@ -63,11 +56,19 @@ socket_message[1] = function(id, size, data)
s.read_required = nil s.read_required = nil
wakeup(s) wakeup(s)
end end
elseif rrt == "string" then else
-- read line if s.buffer_limit and sz > s.buffer_limit then
if driver.readline(s.buffer,nil,rr) then skynet.error(string.format("socket buffer overlow: fd=%d size=%d", id , sz))
s.read_required = nil driver.clear(s.buffer,buffer_pool)
wakeup(s) driver.close(id)
return
end
if rrt == "string" then
-- read line
if driver.readline(s.buffer,nil,rr) then
s.read_required = nil
wakeup(s)
end
end end
end end
end end
@@ -130,7 +131,7 @@ skynet.register_protocol {
local function connect(id, func) local function connect(id, func)
local newbuffer local newbuffer
if func == nil then if func == nil then
newbuffer = driver.buffer(buffer_limit) newbuffer = driver.buffer()
end end
local s = { local s = {
id = id, id = id,
@@ -206,6 +207,27 @@ end
function socket.read(id, sz) function socket.read(id, sz)
local s = socket_pool[id] local s = socket_pool[id]
assert(s) assert(s)
if sz == nil then
-- read some bytes
local ret = driver.readall(s.buffer, buffer_pool)
if ret ~= "" then
return ret
end
if not s.connected then
return false, ret
end
assert(not s.read_required)
s.read_required = 0
suspend(s)
ret = driver.readall(s.buffer, buffer_pool)
if ret ~= "" then
return ret
else
return false, ret
end
end
local ret = driver.pop(s.buffer, buffer_pool, sz) local ret = driver.pop(s.buffer, buffer_pool, sz)
if ret then if ret then
return ret return ret
@@ -325,8 +347,9 @@ function socket.abandon(id)
socket_pool[id] = nil socket_pool[id] = nil
end end
function socket.limit(limit) function socket.limit(id, limit)
buffer_limit = limit local s = assert(socket_pool[id])
s.buffer_limit = limit
end end
return socket return socket

View File

@@ -7,9 +7,9 @@ local function echo(id)
socket.start(id) socket.start(id)
while true do while true do
local str = socket.readline(id,"\n") local str = socket.read(id)
if str then if str then
socket.write(id, str .. "\n") socket.write(id, str)
else else
socket.close(id) socket.close(id)
return return
@@ -21,9 +21,6 @@ if mode == "agent" then
id = tonumber(id) id = tonumber(id)
skynet.start(function() skynet.start(function()
-- A small limit, if socket buffer overflow, close the client
socket.limit(64)
skynet.fork(function() skynet.fork(function()
echo(id) echo(id)
skynet.exit() skynet.exit()