# HG changeset patch # User jbe # Date 1426630008 -3600 # Node ID e835cda61478aa6ac8442aaa7536c107b8515808 # Parent f2efab1ba3d067c31251f41385bdbad31e0522d1 Added io_error_handler; Code cleanup diff -r f2efab1ba3d0 -r e835cda61478 moonbridge_http.lua --- a/moonbridge_http.lua Mon Mar 09 16:42:55 2015 +0100 +++ b/moonbridge_http.lua Tue Mar 17 23:06:48 2015 +0100 @@ -178,13 +178,23 @@ t[#t+1] = "" preamble = table.concat(t, "\r\n") end + local function assert_io(retval, errmsg) + if retval then + return retval + end + if options.io_error_handler then + options.io_error_handler(errmsg) + error(errmsg) + end + error(errmsg, 2) + end + -- desired chunk sizes: + local input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384 + local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024 -- return connect handler: return function(socket) local survive = true -- set to false if process shall be terminated later repeat - -- desired chunk sizes: - local input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384 - local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024 -- process named arguments "request_header_size_limit" and "request_body_size_limit": local remaining_header_size_limit = options.request_header_size_limit or 1024*1024 local remaining_body_size_limit = options.request_body_size_limit or 64*1024*1024 @@ -226,7 +236,7 @@ end local chunk = socket:read(limit) if not chunk or #chunk ~= limit then - error("Unexpected EOF while reading chunk of request body") + assert_io(false, "Unexpected EOF or read error while reading chunk of request body") end remaining = remaining - limit if callback then @@ -245,22 +255,22 @@ while socket.input:read(input_chunk_size) do end end) -- fully close socket: - assert(socket:close()) + assert_io(socket:close()) else - assert(socket:flush()) + assert_io(socket:flush()) request:stream_request_body() end end -- writes out buffered chunks (without flushing the socket): local function send_chunk() if chunk_bytes > 0 then - assert(socket:write(string.format("%x\r\n", chunk_bytes))) + assert_io(socket:write(string.format("%x\r\n", chunk_bytes))) for i = 1, #chunk_parts do - assert(socket:write(chunk_parts[i])) + assert_io(socket:write(chunk_parts[i])) end chunk_parts = {} chunk_bytes = 0 - assert(socket:write("\r\n")) + assert_io(socket:write("\r\n")) end end -- terminate header section in response, optionally flushing: @@ -271,28 +281,28 @@ elseif output_state == "finished" then error("Response has already been finished") elseif output_state == "info_status_sent" then - assert(socket:write("\r\n")) - assert(socket:flush()) + assert_io(socket:write("\r\n")) + assert_io(socket:flush()) output_state = "no_status_sent" elseif output_state == "bodyless_status_sent" then if connection_close_requested and not connection_close_responded then request:send_header("Connection", "close") end - assert(socket:write("\r\n")) + assert_io(socket:write("\r\n")) finish_response() output_state = "finished" elseif output_state == "status_sent" then if not content_length then - assert(socket:write("Transfer-Encoding: chunked\r\n")) + assert_io(socket:write("Transfer-Encoding: chunked\r\n")) end if connection_close_requested and not connection_close_responded then request:send_header("Connection", "close") end - assert(socket:write("\r\n")) + assert_io(socket:write("\r\n")) if request.method == "HEAD" then finish_response() elseif flush then - assert(socket:flush()) + assert_io(socket:flush()) end output_state = "headers_sent" elseif output_state ~= "headers_sent" then @@ -311,14 +321,14 @@ request:process_request_body() end if output_state == "info_status_sent" then - assert(socket:write("\r\n")) - assert(socket:flush()) + assert_io(socket:write("\r\n")) + assert_io(socket:flush()) elseif output_state ~= "no_status_sent" then error("HTTP status has already been sent") end local status1 = string.sub(value, 1, 1) local status3 = string.sub(value, 1, 3) - assert(socket:write("HTTP/1.1 ", value, "\r\n", preamble)) + assert_io(socket:write("HTTP/1.1 ", value, "\r\n", preamble)) local without_response_body = status_without_response_body[status3] if without_response_body then output_state = "bodyless_status_sent" @@ -371,7 +381,7 @@ end end end - assert(socket:write(key, ": ", value, "\r\n")) + assert_io(socket:write(key, ": ", value, "\r\n")) end, -- method to finish and flush headers: finish_headers = function() @@ -397,11 +407,11 @@ if content_length then local bytes_to_send = #str if bytes_sent + bytes_to_send > content_length then - assert(socket:write(string.sub(str, 1, content_length - bytes_sent))) + assert_io(socket:write(string.sub(str, 1, content_length - bytes_sent))) bytes_sent = content_length error("Content length exceeded") else - assert(socket:write(str)) + assert_io(socket:write(str)) bytes_sent = bytes_sent + bytes_to_send end else @@ -417,7 +427,7 @@ -- flush output buffer: flush = function(self) send_chunk() - assert(socket:flush()) + assert_io(socket:flush()) end, -- finish response: finish = function(self) @@ -435,7 +445,7 @@ end else send_chunk() - assert(socket:write("0\r\n\r\n")) + assert_io(socket:write("0\r\n\r\n")) end finish_response() end @@ -742,8 +752,13 @@ if request.headers_flags["Transfer-Encoding"]["chunked"] then while true do local line = socket:readuntil("\n", 32 + remaining_body_size_limit) - if not line then - error("Unexpected EOF while reading next chunk of request body") + if not ( + line and ( + string.match(line, "^[0-9A-Fa-f]+\r?$") or + string.match(line, "^[0-9A-Fa-f]+[ \t;]") + ) + ) then + assert_io(false, "Unexpected EOF or read error while reading next chunk of request body") end local zeros, lenstr = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)\r?\n$") local chunkext @@ -753,7 +768,7 @@ zeros, lenstr, chunkext = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)([ \t;].-)\r?\n$") end if not lenstr or #lenstr > 13 then - error("Encoding error or unexpected EOF while reading chunk of request body") + error("Encoding error while reading chunk of request body") end local len = tonumber("0x" .. lenstr) remaining_body_size_limit = remaining_body_size_limit - (#zeros + #chunkext + len) diff -r f2efab1ba3d0 -r e835cda61478 reference.txt --- a/reference.txt Mon Mar 09 16:42:55 2015 +0100 +++ b/reference.txt Tue Mar 17 23:06:48 2015 +0100 @@ -163,6 +163,8 @@ ignored when request:flush() is called) - static_headers: a set of headers to be included in every HTTP response (may be a string, a table or strings, or a table of key-value pairs) +- io_error_handler: a function to be called when an I/O operation with the + client fails (must not return or an error will be raised automatically) The callback function receives a single request object as argument, which is described below. @@ -420,4 +422,10 @@ indicated through return of request:stream_request_body(...) (not by calling the callback without arguments). +The function may be called with nil instead of a callback function. In this +case, the request body is read and discarded. Only if nil is passed instead of +a callback, then the function may also be invoked when the request body has +already been read and/or processed. In the latter case, the function performs +no operation. +