moonbridge
changeset 44:e835cda61478
Added io_error_handler; Code cleanup
author | jbe |
---|---|
date | Tue Mar 17 23:06:48 2015 +0100 (2015-03-17) |
parents | f2efab1ba3d0 |
children | ab51824e139b |
files | moonbridge_http.lua reference.txt |
line diff
1.1 --- a/moonbridge_http.lua Mon Mar 09 16:42:55 2015 +0100 1.2 +++ b/moonbridge_http.lua Tue Mar 17 23:06:48 2015 +0100 1.3 @@ -178,13 +178,23 @@ 1.4 t[#t+1] = "" 1.5 preamble = table.concat(t, "\r\n") 1.6 end 1.7 + local function assert_io(retval, errmsg) 1.8 + if retval then 1.9 + return retval 1.10 + end 1.11 + if options.io_error_handler then 1.12 + options.io_error_handler(errmsg) 1.13 + error(errmsg) 1.14 + end 1.15 + error(errmsg, 2) 1.16 + end 1.17 + -- desired chunk sizes: 1.18 + local input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384 1.19 + local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024 1.20 -- return connect handler: 1.21 return function(socket) 1.22 local survive = true -- set to false if process shall be terminated later 1.23 repeat 1.24 - -- desired chunk sizes: 1.25 - local input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384 1.26 - local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024 1.27 -- process named arguments "request_header_size_limit" and "request_body_size_limit": 1.28 local remaining_header_size_limit = options.request_header_size_limit or 1024*1024 1.29 local remaining_body_size_limit = options.request_body_size_limit or 64*1024*1024 1.30 @@ -226,7 +236,7 @@ 1.31 end 1.32 local chunk = socket:read(limit) 1.33 if not chunk or #chunk ~= limit then 1.34 - error("Unexpected EOF while reading chunk of request body") 1.35 + assert_io(false, "Unexpected EOF or read error while reading chunk of request body") 1.36 end 1.37 remaining = remaining - limit 1.38 if callback then 1.39 @@ -245,22 +255,22 @@ 1.40 while socket.input:read(input_chunk_size) do end 1.41 end) 1.42 -- fully close socket: 1.43 - assert(socket:close()) 1.44 + assert_io(socket:close()) 1.45 else 1.46 - assert(socket:flush()) 1.47 + assert_io(socket:flush()) 1.48 request:stream_request_body() 1.49 end 1.50 end 1.51 -- writes out buffered chunks (without flushing the socket): 1.52 local function send_chunk() 1.53 if chunk_bytes > 0 then 1.54 - assert(socket:write(string.format("%x\r\n", chunk_bytes))) 1.55 + assert_io(socket:write(string.format("%x\r\n", chunk_bytes))) 1.56 for i = 1, #chunk_parts do 1.57 - assert(socket:write(chunk_parts[i])) 1.58 + assert_io(socket:write(chunk_parts[i])) 1.59 end 1.60 chunk_parts = {} 1.61 chunk_bytes = 0 1.62 - assert(socket:write("\r\n")) 1.63 + assert_io(socket:write("\r\n")) 1.64 end 1.65 end 1.66 -- terminate header section in response, optionally flushing: 1.67 @@ -271,28 +281,28 @@ 1.68 elseif output_state == "finished" then 1.69 error("Response has already been finished") 1.70 elseif output_state == "info_status_sent" then 1.71 - assert(socket:write("\r\n")) 1.72 - assert(socket:flush()) 1.73 + assert_io(socket:write("\r\n")) 1.74 + assert_io(socket:flush()) 1.75 output_state = "no_status_sent" 1.76 elseif output_state == "bodyless_status_sent" then 1.77 if connection_close_requested and not connection_close_responded then 1.78 request:send_header("Connection", "close") 1.79 end 1.80 - assert(socket:write("\r\n")) 1.81 + assert_io(socket:write("\r\n")) 1.82 finish_response() 1.83 output_state = "finished" 1.84 elseif output_state == "status_sent" then 1.85 if not content_length then 1.86 - assert(socket:write("Transfer-Encoding: chunked\r\n")) 1.87 + assert_io(socket:write("Transfer-Encoding: chunked\r\n")) 1.88 end 1.89 if connection_close_requested and not connection_close_responded then 1.90 request:send_header("Connection", "close") 1.91 end 1.92 - assert(socket:write("\r\n")) 1.93 + assert_io(socket:write("\r\n")) 1.94 if request.method == "HEAD" then 1.95 finish_response() 1.96 elseif flush then 1.97 - assert(socket:flush()) 1.98 + assert_io(socket:flush()) 1.99 end 1.100 output_state = "headers_sent" 1.101 elseif output_state ~= "headers_sent" then 1.102 @@ -311,14 +321,14 @@ 1.103 request:process_request_body() 1.104 end 1.105 if output_state == "info_status_sent" then 1.106 - assert(socket:write("\r\n")) 1.107 - assert(socket:flush()) 1.108 + assert_io(socket:write("\r\n")) 1.109 + assert_io(socket:flush()) 1.110 elseif output_state ~= "no_status_sent" then 1.111 error("HTTP status has already been sent") 1.112 end 1.113 local status1 = string.sub(value, 1, 1) 1.114 local status3 = string.sub(value, 1, 3) 1.115 - assert(socket:write("HTTP/1.1 ", value, "\r\n", preamble)) 1.116 + assert_io(socket:write("HTTP/1.1 ", value, "\r\n", preamble)) 1.117 local without_response_body = status_without_response_body[status3] 1.118 if without_response_body then 1.119 output_state = "bodyless_status_sent" 1.120 @@ -371,7 +381,7 @@ 1.121 end 1.122 end 1.123 end 1.124 - assert(socket:write(key, ": ", value, "\r\n")) 1.125 + assert_io(socket:write(key, ": ", value, "\r\n")) 1.126 end, 1.127 -- method to finish and flush headers: 1.128 finish_headers = function() 1.129 @@ -397,11 +407,11 @@ 1.130 if content_length then 1.131 local bytes_to_send = #str 1.132 if bytes_sent + bytes_to_send > content_length then 1.133 - assert(socket:write(string.sub(str, 1, content_length - bytes_sent))) 1.134 + assert_io(socket:write(string.sub(str, 1, content_length - bytes_sent))) 1.135 bytes_sent = content_length 1.136 error("Content length exceeded") 1.137 else 1.138 - assert(socket:write(str)) 1.139 + assert_io(socket:write(str)) 1.140 bytes_sent = bytes_sent + bytes_to_send 1.141 end 1.142 else 1.143 @@ -417,7 +427,7 @@ 1.144 -- flush output buffer: 1.145 flush = function(self) 1.146 send_chunk() 1.147 - assert(socket:flush()) 1.148 + assert_io(socket:flush()) 1.149 end, 1.150 -- finish response: 1.151 finish = function(self) 1.152 @@ -435,7 +445,7 @@ 1.153 end 1.154 else 1.155 send_chunk() 1.156 - assert(socket:write("0\r\n\r\n")) 1.157 + assert_io(socket:write("0\r\n\r\n")) 1.158 end 1.159 finish_response() 1.160 end 1.161 @@ -742,8 +752,13 @@ 1.162 if request.headers_flags["Transfer-Encoding"]["chunked"] then 1.163 while true do 1.164 local line = socket:readuntil("\n", 32 + remaining_body_size_limit) 1.165 - if not line then 1.166 - error("Unexpected EOF while reading next chunk of request body") 1.167 + if not ( 1.168 + line and ( 1.169 + string.match(line, "^[0-9A-Fa-f]+\r?$") or 1.170 + string.match(line, "^[0-9A-Fa-f]+[ \t;]") 1.171 + ) 1.172 + ) then 1.173 + assert_io(false, "Unexpected EOF or read error while reading next chunk of request body") 1.174 end 1.175 local zeros, lenstr = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)\r?\n$") 1.176 local chunkext 1.177 @@ -753,7 +768,7 @@ 1.178 zeros, lenstr, chunkext = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)([ \t;].-)\r?\n$") 1.179 end 1.180 if not lenstr or #lenstr > 13 then 1.181 - error("Encoding error or unexpected EOF while reading chunk of request body") 1.182 + error("Encoding error while reading chunk of request body") 1.183 end 1.184 local len = tonumber("0x" .. lenstr) 1.185 remaining_body_size_limit = remaining_body_size_limit - (#zeros + #chunkext + len)
2.1 --- a/reference.txt Mon Mar 09 16:42:55 2015 +0100 2.2 +++ b/reference.txt Tue Mar 17 23:06:48 2015 +0100 2.3 @@ -163,6 +163,8 @@ 2.4 ignored when request:flush() is called) 2.5 - static_headers: a set of headers to be included in every HTTP response 2.6 (may be a string, a table or strings, or a table of key-value pairs) 2.7 +- io_error_handler: a function to be called when an I/O operation with the 2.8 + client fails (must not return or an error will be raised automatically) 2.9 2.10 The callback function receives a single request object as argument, which is 2.11 described below. 2.12 @@ -420,4 +422,10 @@ 2.13 indicated through return of request:stream_request_body(...) (not by calling 2.14 the callback without arguments). 2.15 2.16 +The function may be called with nil instead of a callback function. In this 2.17 +case, the request body is read and discarded. Only if nil is passed instead of 2.18 +a callback, then the function may also be invoked when the request body has 2.19 +already been read and/or processed. In the latter case, the function performs 2.20 +no operation. 2.21 2.22 +