aboutsummaryrefslogtreecommitdiffstats
path: root/net/http
diff options
context:
space:
mode:
Diffstat (limited to 'net/http')
-rw-r--r--net/http/codes.lua15
-rw-r--r--net/http/parser.lua8
-rw-r--r--net/http/server.lua92
3 files changed, 89 insertions, 26 deletions
diff --git a/net/http/codes.lua b/net/http/codes.lua
index 0cadd079..1090e545 100644
--- a/net/http/codes.lua
+++ b/net/http/codes.lua
@@ -25,6 +25,7 @@ local response_codes = {
[305] = "Use Proxy";
-- The 306 status code was used in a previous version of [RFC2616], is no longer used, and the code is reserved.
[307] = "Temporary Redirect";
+ [308] = "Permanent Redirect";
[400] = "Bad Request";
[401] = "Unauthorized";
@@ -39,17 +40,22 @@ local response_codes = {
[410] = "Gone";
[411] = "Length Required";
[412] = "Precondition Failed";
- [413] = "Request Entity Too Large";
- [414] = "Request-URI Too Long";
+ [413] = "Payload Too Large";
+ [414] = "URI Too Long";
[415] = "Unsupported Media Type";
- [416] = "Requested Range Not Satisfiable";
+ [416] = "Range Not Satisfiable";
[417] = "Expectation Failed";
[418] = "I'm a teapot";
+ [421] = "Misdirected Request";
[422] = "Unprocessable Entity";
[423] = "Locked";
[424] = "Failed Dependency";
-- The 425 status code is reserved for the WebDAV advanced collections expired proposal [RFC2817]
[426] = "Upgrade Required";
+ [428] = "Precondition Required";
+ [429] = "Too Many Requests";
+ [431] = "Request Header Fields Too Large";
+ [451] = "Unavailable For Legal Reasons";
[500] = "Internal Server Error";
[501] = "Not Implemented";
@@ -61,7 +67,8 @@ local response_codes = {
[507] = "Insufficient Storage";
[508] = "Loop Detected";
[510] = "Not Extended";
+ [511] = "Network Authentication Required";
};
for k,v in pairs(response_codes) do response_codes[k] = k.." "..v; end
-return setmetatable(response_codes, { __index = function(t, k) return k.." Unassigned"; end })
+return setmetatable(response_codes, { __index = function(_, k) return k.." Unassigned"; end })
diff --git a/net/http/parser.lua b/net/http/parser.lua
index 1e698728..4e4ae9fb 100644
--- a/net/http/parser.lua
+++ b/net/http/parser.lua
@@ -38,7 +38,7 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb)
local have_body;
local error;
return {
- feed = function(self, data)
+ feed = function(_, data)
if error then return nil, "parse has failed"; end
if not data then -- EOF
if buftable then buf, buftable = t_concat(buf), false; end
@@ -46,7 +46,7 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb)
packet.body = buf;
success_cb(packet);
elseif buf ~= "" then -- unexpected EOF
- error = true; return error_cb();
+ error = true; return error_cb("unexpected-eof");
end
return;
end
@@ -134,6 +134,9 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb)
if state then -- read body
if client then
if chunked then
+ if chunk_start and buflen - chunk_start - 2 < chunk_size then
+ return;
+ end -- not enough data
if buftable then buf, buftable = t_concat(buf), false; end
if not buf:find("\r\n", nil, true) then
return;
@@ -150,6 +153,7 @@ function httpstream.new(success_cb, error_cb, parser_type, options_cb)
elseif buflen - chunk_start - 2 >= chunk_size then -- we have a chunk
packet.body = packet.body..buf:sub(chunk_start, chunk_start + (chunk_size-1));
buf = buf:sub(chunk_start + chunk_size + 2);
+ buflen = buflen - (chunk_start + chunk_size + 2 - 1);
chunk_size, chunk_start = nil, nil;
else -- Partial chunk remaining
break;
diff --git a/net/http/server.lua b/net/http/server.lua
index 32cda8aa..877c7f17 100644
--- a/net/http/server.lua
+++ b/net/http/server.lua
@@ -11,11 +11,14 @@ local setmetatable = setmetatable;
local xpcall = xpcall;
local traceback = debug.traceback;
local tostring = tostring;
+local cache = require "util.cache";
local codes = require "net.http.codes";
+local blocksize = 2^16;
local _M = {};
local sessions = {};
+local incomplete = {};
local listener = {};
local hosts = {};
local default_host;
@@ -28,7 +31,10 @@ local function is_wildcard_match(wildcard_event, event)
return wildcard_event:sub(1, -2) == event:sub(1, #wildcard_event-1);
end
-local recent_wildcard_events, max_cached_wildcard_events = {}, 10000;
+local _handlers = events._handlers;
+local recent_wildcard_events = cache.new(10000, function (key, value) -- luacheck: ignore 212/value
+ rawset(_handlers, key, nil);
+end);
local event_map = events._event_map;
setmetatable(events._handlers, {
@@ -63,10 +69,7 @@ setmetatable(events._handlers, {
end
rawset(handlers, curr_event, handlers_array);
if not event_map[curr_event] then -- Only wildcard handlers match, if any
- table.insert(recent_wildcard_events, curr_event);
- if #recent_wildcard_events > max_cached_wildcard_events then
- rawset(handlers, table.remove(recent_wildcard_events, 1), nil);
- end
+ recent_wildcard_events:set(curr_event, true);
end
return handlers_array;
end;
@@ -143,17 +146,26 @@ function listener.ondisconnect(conn)
open_response.finished = true;
open_response:on_destroy();
end
+ incomplete[conn] = nil;
sessions[conn] = nil;
end
function listener.ondetach(conn)
sessions[conn] = nil;
+ incomplete[conn] = nil;
end
function listener.onincoming(conn, data)
sessions[conn]:feed(data);
end
+function listener.ondrain(conn)
+ local response = incomplete[conn];
+ if response and response._send_more then
+ response._send_more();
+ end
+end
+
local headerfix = setmetatable({}, {
__index = function(t, k)
local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": ";
@@ -162,7 +174,7 @@ local headerfix = setmetatable({}, {
end
});
-function _M.hijack_response(response, listener)
+function _M.hijack_response(response, listener) -- luacheck: ignore
error("TODO");
end
function handle_request(conn, request, finish_cb)
@@ -193,6 +205,8 @@ function handle_request(conn, request, finish_cb)
persistent = persistent;
conn = conn;
send = _M.send_response;
+ send_file = _M.send_file;
+ done = _M.finish_response;
finish_cb = finish_cb;
};
conn._http_open_response = response;
@@ -212,10 +226,10 @@ function handle_request(conn, request, finish_cb)
err_code, err = 400, "Missing or invalid 'Host' header";
end
end
-
+
if err then
response.status_code = err_code;
- response:send(events.fire_event("http-error", { code = err_code, message = err }));
+ response:send(events.fire_event("http-error", { code = err_code, message = err, response = response }));
return;
end
@@ -230,7 +244,8 @@ function handle_request(conn, request, finish_cb)
if result_type == "number" then
response.status_code = result;
if result >= 400 then
- body = events.fire_event("http-error", { code = result });
+ payload.code = result;
+ body = events.fire_event("http-error", payload);
end
elseif result_type == "string" then
body = result;
@@ -252,26 +267,63 @@ function handle_request(conn, request, finish_cb)
-- if handler not called, return 404
response.status_code = 404;
- response:send(events.fire_event("http-error", { code = 404 }));
+ payload.code = 404;
+ response:send(events.fire_event("http-error", payload));
end
-function _M.send_response(response, body)
- if response.finished then return; end
- response.finished = true;
- response.conn._http_open_response = nil;
-
+local function prepare_header(response)
local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]);
local headers = response.headers;
- body = body or response.body or "";
- headers.content_length = #body;
-
local output = { status_line };
for k,v in pairs(headers) do
t_insert(output, headerfix[k]..v);
end
t_insert(output, "\r\n\r\n");
+ return output;
+end
+_M.prepare_header = prepare_header;
+function _M.send_response(response, body)
+ if response.finished then return; end
+ body = body or response.body or "";
+ response.headers.content_length = #body;
+ local output = prepare_header(response);
t_insert(output, body);
-
response.conn:write(t_concat(output));
+ response:done();
+end
+function _M.send_file(response, f)
+ if response.finished then return; end
+ local chunked = not response.headers.content_length;
+ if chunked then response.headers.transfer_encoding = "chunked"; end
+ incomplete[response.conn] = response;
+ response._send_more = function ()
+ if response.finished then
+ incomplete[response.conn] = nil;
+ return;
+ end
+ local chunk = f:read(blocksize);
+ if chunk then
+ if chunked then
+ chunk = ("%x\r\n%s\r\n"):format(#chunk, chunk);
+ end
+ -- io.write("."); io.flush();
+ response.conn:write(chunk);
+ else
+ if chunked then
+ response.conn:write("0\r\n\r\n");
+ end
+ -- io.write("\n");
+ if f.close then f:close(); end
+ incomplete[response.conn] = nil;
+ return response:done();
+ end
+ end
+ response.conn:write(t_concat(prepare_header(response)));
+ return true;
+end
+function _M.finish_response(response)
+ if response.finished then return; end
+ response.finished = true;
+ response.conn._http_open_response = nil;
if response.on_destroy then
response:on_destroy();
response.on_destroy = nil;
@@ -290,7 +342,7 @@ function _M.remove_handler(event, handler)
end
function _M.listen_on(port, interface, ssl)
- addserver(interface or "*", port, listener, "*a", ssl);
+ return addserver(interface or "*", port, listener, "*a", ssl);
end
function _M.add_host(host)
hosts[host] = true;