moonbridge
changeset 160:573995950b0b
Restarted work on new HTTP module implementation
author | jbe |
---|---|
date | Thu Jun 11 00:49:54 2015 +0200 (2015-06-11) |
parents | bd7225b30391 |
children | d5b8295d035e |
files | moonbridge_http.lua |
line diff
1.1 --- a/moonbridge_http.lua Fri Jun 05 19:53:41 2015 +0200 1.2 +++ b/moonbridge_http.lua Thu Jun 11 00:49:54 2015 +0200 1.3 @@ -108,105 +108,23 @@ 1.4 return newtbl 1.5 end 1.6 1.7 -local headers_mt_self = setmetatable({}, {__mode="k"}) 1.8 - 1.9 -local headers_mts = { 1.10 - headers_mt = { 1.11 - __index = function(tbl, key) 1.12 - local self = headers_mt_self[tbl] 1.13 - local lowerkey = string.lower(key) 1.14 - local result = self._headers[lowerkey] 1.15 - if result == nil then 1.16 - result = {} 1.17 - end 1.18 - tbl[lowerkey] = result 1.19 - tbl[key] = result 1.20 - return result 1.21 - end 1.22 - }, 1.23 - -- table mapping header field names to value-lists 1.24 - -- (for headers with comma separated values): 1.25 - headers_csv_table = { 1.26 - __index = function(tbl, key) 1.27 - local self = headers_mt_self[tbl] 1.28 - local result = {} 1.29 - for i, line in ipairs(self.headers[key]) do 1.30 - for entry in string.gmatch(line, "[^,]+") do 1.31 - local value = string.match(entry, "^[ \t]*(..-)[ \t]*$") 1.32 - if value then 1.33 - result[#result+1] = value 1.34 - end 1.35 - end 1.36 - end 1.37 - tbl[key] = result 1.38 - return result 1.39 - end 1.40 - }, 1.41 - -- table mapping header field names to a comma separated string 1.42 - -- (for headers with comma separated values): 1.43 - headers_csv_string = { 1.44 - __index = function(tbl, key) 1.45 - local self = headers_mt_self[tbl] 1.46 - local result = {} 1.47 - for i, line in ipairs(self.headers[key]) do 1.48 - result[#result+1] = line 1.49 - end 1.50 - result = string.concat(result, ", ") 1.51 - tbl[key] = result 1.52 - return result 1.53 +function generate_handler(handler, options) 1.54 + -- swap arguments if necessary (for convenience): 1.55 + if type(handler) ~= "function" and type(options) == "function" then 1.56 + handler, options = options, handler 1.57 + end 1.58 + -- helper function to process options: 1.59 + local function default(name, default_value) 1.60 + local value = options[name] 1.61 + if value == nil then 1.62 + return default_value 1.63 + else 1.64 + return value or nil 1.65 end 1.66 - }, 1.67 - -- table mapping header field names to a single string value 1.68 - -- (or false if header has been sent multiple times): 1.69 - headers_value = { 1.70 - __index = function(tbl, key) 1.71 - local self = headers_mt_self[tbl] 1.72 - if self._headers_value_nil[key] then 1.73 - return nil 1.74 - end 1.75 - local result = nil 1.76 - local values = self.headers_csv_table[key] 1.77 - if #values == 0 then 1.78 - self._headers_value_nil[key] = true 1.79 - elseif #values == 1 then 1.80 - result = values[1] 1.81 - else 1.82 - result = false 1.83 - end 1.84 - tbl[key] = result 1.85 - return result 1.86 - end 1.87 - }, 1.88 - -- table mapping header field names to a flag table, 1.89 - -- indicating if the comma separated value contains certain entries: 1.90 - headers_flags = { 1.91 - __index = function(tbl, key) 1.92 - local self = headers_mt_self[tbl] 1.93 - local result = setmetatable({}, { 1.94 - __index = function(tbl, key) 1.95 - local lowerkey = string.lower(key) 1.96 - local result = rawget(tbl, lowerkey) or false 1.97 - tbl[lowerkey] = result 1.98 - tbl[key] = result 1.99 - return result 1.100 - end 1.101 - }) 1.102 - for i, value in ipairs(self.headers_csv_table[key]) do 1.103 - result[string.lower(value)] = true 1.104 - end 1.105 - tbl[key] = result 1.106 - return result 1.107 - end 1.108 - } 1.109 -} 1.110 - 1.111 -request_pt = {} 1.112 -request_mt = { __index = request_pt } 1.113 - 1.114 -function request_pt:_init(handler, options) 1.115 - self._application_handler = handler 1.116 + end 1.117 -- process options: 1.118 options = options or {} 1.119 + local preamble = "" -- preamble sent with every(!) HTTP response 1.120 do 1.121 -- named arg "static_headers" is used to create the preamble: 1.122 local s = options.static_headers 1.123 @@ -227,549 +145,53 @@ 1.124 end 1.125 end 1.126 t[#t+1] = "" 1.127 - self._preamble = table.concat(t, "\r\n") -- preamble sent with every(!) HTTP response 1.128 - end 1.129 - self._input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384 1.130 - self._output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024 1.131 - self._header_size_limit = options.header_size_limit or 1024*1024 1.132 - self._body_size_limit = options.body_size_limit or 64*1024*1024 1.133 - local function init_timeout(name, default) 1.134 - local value = options[name] 1.135 - if value == nil then 1.136 - self["_"..name] = default 1.137 - else 1.138 - self["_"..name] = value 1.139 - end 1.140 - end 1.141 - init_timeout("request_idle_timeout", 330) 1.142 - init_timeout("request_header_timeout", 30) 1.143 - init_timeout("request_body_timeout", 1800) 1.144 - init_timeout("response_timeout", 1830) 1.145 - self._poll = options.poll_function or moonbridge_io.poll 1.146 - self:_create_closure("_write_yield") 1.147 - self:_create_closure("_handler") 1.148 - self:_create_header_metatables() 1.149 -end 1.150 - 1.151 -function request_pt:_create_closure(name) 1.152 - self[name.."_closure"] = function(...) 1.153 - return self[name](self, ...) 1.154 - end 1.155 -end 1.156 - 1.157 -function request_pt:_handler(socket) 1.158 - self._socket = socket 1.159 - self._survive = true 1.160 - self._socket_set = {[socket] = true} 1.161 - self._faulty = false 1.162 - self._state = "config" 1.163 - self._connection_close_requested = false 1.164 - self._connection_close_responded = false 1.165 - for name, mt in pairs(headers_mts) do 1.166 - local tbl = setmetatable({}, mt) 1.167 - headers_mt_self[tbl] = self 1.168 - self[name] = tbl 1.169 - end 1.170 - repeat 1.171 - -- wait for input: 1.172 - if not self._poll(self._socket_set, nil, self._request_idle_timeout) then 1.173 - self:_error("408 Request Timeout", "Idle connection timed out") 1.174 - return self._survive 1.175 - end 1.176 - -- read headers (with timeout): 1.177 - do 1.178 - local coro = coroutine.wrap(self._read_headers) 1.179 - local timeout = self._request_header_timeout 1.180 - local starttime = timeout and moonbridge_io.timeref() 1.181 - while true do 1.182 - local status = coro(self) 1.183 - if status == nil then 1.184 - local remaining 1.185 - if timeout then 1.186 - remaining = timeout - moonbridge_io.timeref(starttime) 1.187 - end 1.188 - if not self._poll(self._socket_set, nil, remaining) then 1.189 - self:_error("408 Request Timeout", "Timeout while receiving headers") 1.190 - return self._survive 1.191 - end 1.192 - elseif status == false then 1.193 - return self._survive 1.194 - elseif status == true then 1.195 - break 1.196 - else 1.197 - error("Unexpected yield value") 1.198 - end 1.199 - end 1.200 - end 1.201 - -- prepare reading of body: 1.202 - self._read_body_coro = coroutine.wrap(self._read_body) --TODO? 1.203 - -- set timeout for application handler: 1.204 - timeout(self._response_timeout or 0) 1.205 - -- call application handler: 1.206 - if self._application_handler(self) ~= true then 1.207 - self._survive = false 1.208 - end 1.209 - -- enforce request:finish() 1.210 - request:finish() 1.211 - -- reset timeout of application handler 1.212 - timeout(0) 1.213 - until self._connection_close_responded 1.214 - return self._survive 1.215 -end 1.216 - 1.217 -function request_pt:_prepare_body() 1.218 - self:_assert_not_faulty() 1.219 - if self._state == "prepare" then 1.220 - error("Unexpected state in HTTP module") 1.221 - elseif self._state ~= "config" then 1.222 - return 1.223 + preamble = table.concat(t, "\r\n") 1.224 end 1.225 - self._state = "prepare" 1.226 - local content_type = self.headers_value["Content-Type"] 1.227 - if content_type then 1.228 - if 1.229 - content_type == "application/x-www-form-urlencoded" or 1.230 - string.match(content_type, "^application/x%-www%-form%-urlencoded *;") 1.231 - then 1.232 - self._consume_all_input() 1.233 - self.post_params_list = read_urlencoded_form(self.body) 1.234 - else 1.235 - local boundary = string.match( 1.236 - content_type, 1.237 - '^multipart/form%-data[ \t]*[;,][ \t]*boundary="([^"]+)"$' 1.238 - ) or string.match( 1.239 - content_type, 1.240 - '^multipart/form%-data[ \t]*[;,][ \t]*boundary=([^"; \t]+)$' 1.241 - ) 1.242 - if boundary then 1.243 - self.post_metadata_list = {} 1.244 - boundary = "--" .. boundary 1.245 - local headerdata = "" 1.246 - local streamer 1.247 - local field_name 1.248 - local metadata = {} 1.249 - local value_parts 1.250 - local function default_streamer(chunk) 1.251 - value_parts[#value_parts+1] = chunk 1.252 - end 1.253 - local function stream_part_finish() 1.254 - if streamer == default_streamer then 1.255 - local value = table.concat(value_parts) 1.256 - value_parts = nil 1.257 - if field_name then 1.258 - local values = self.post_params_list[field_name] 1.259 - values[#values+1] = value 1.260 - local metadata_entries = post_metadata_list[field_name] 1.261 - metadata_entries[#metadata_entries+1] = metadata 1.262 - end 1.263 - else 1.264 - streamer() 1.265 - end 1.266 - headerdata = "" 1.267 - streamer = nil 1.268 - field_name = nil 1.269 - metadata = {} 1.270 - end 1.271 - local function stream_part_chunk(chunk) 1.272 - if streamer then 1.273 - streamer(chunk) 1.274 - else 1.275 - headerdata = headerdata .. chunk 1.276 - while true do 1.277 - local line, remaining = string.match(headerdata, "^(.-)\r?\n(.*)$") 1.278 - if not line then 1.279 - break 1.280 - end 1.281 - if line == "" then 1.282 - streamer = streamed_post_params[field_name] 1.283 - if not streamer then 1.284 - for i, rule in ipairs(streamed_post_param_patterns) do 1.285 - if string.match(field_name, rule[1]) then 1.286 - streamer = rule[2] 1.287 - break 1.288 - end 1.289 - end 1.290 - end 1.291 - if not streamer then 1.292 - value_parts = {} 1.293 - streamer = default_streamer 1.294 - end 1.295 - streamer(remaining, field_name, metadata) 1.296 - return 1.297 - end 1.298 - headerdata = remaining 1.299 - local header_key, header_value = string.match(line, "^([^:]*):[ \t]*(.-)[ \t]*$") 1.300 - if not header_key then 1.301 - request_error(true, "400 Bad Request", "Invalid header in multipart/form-data part") 1.302 - end 1.303 - header_key = string.lower(header_key) 1.304 - if header_key == "content-disposition" then 1.305 - local escaped_header_value = string.gsub(header_value, '"[^"]*"', function(str) 1.306 - return string.gsub(str, "=", "==") 1.307 - end) 1.308 - field_name = string.match(escaped_header_value, ';[ \t]*name="([^"]*)"') 1.309 - if field_name then 1.310 - field_name = string.gsub(field_name, "==", "=") 1.311 - else 1.312 - field_name = string.match(header_value, ';[ \t]*name=([^"; \t]+)') 1.313 - end 1.314 - metadata.file_name = string.match(escaped_header_value, ';[ \t]*filename="([^"]*)"') 1.315 - if metadata.file_name then 1.316 - metadata.file_name = string.gsub(metadata.file_name, "==", "=") 1.317 - else 1.318 - string.match(header_value, ';[ \t]*filename=([^"; \t]+)') 1.319 - end 1.320 - elseif header_key == "content-type" then 1.321 - metadata.content_type = header_value 1.322 - elseif header_key == "content-transfer-encoding" then 1.323 - request_error(true, "400 Bad Request", "Content-transfer-encoding not supported by multipart/form-data parser") 1.324 - end 1.325 - end 1.326 - end 1.327 - end 1.328 - local skippart = true -- ignore data until first boundary 1.329 - local afterbound = false -- interpret 2 bytes after boundary ("\r\n" or "--") 1.330 - local terminated = false -- final boundary read 1.331 - local bigchunk = "" 1.332 - request:stream_request_body(function(chunk) 1.333 - if terminated then 1.334 - return 1.335 - end 1.336 - bigchunk = bigchunk .. chunk 1.337 - while true do 1.338 - if afterbound then 1.339 - if #bigchunk <= 2 then 1.340 - return 1.341 - end 1.342 - local terminator = string.sub(bigchunk, 1, 2) 1.343 - if terminator == "\r\n" then 1.344 - afterbound = false 1.345 - bigchunk = string.sub(bigchunk, 3) 1.346 - elseif terminator == "--" then 1.347 - terminated = true 1.348 - bigchunk = nil 1.349 - return 1.350 - else 1.351 - request_error(true, "400 Bad Request", "Error while parsing multipart body (expected CRLF or double minus)") 1.352 - end 1.353 - end 1.354 - local pos1, pos2 = string.find(bigchunk, boundary, 1, true) 1.355 - if not pos1 then 1.356 - if not skippart then 1.357 - local safe = #bigchunk-#boundary 1.358 - if safe > 0 then 1.359 - stream_part_chunk(string.sub(bigchunk, 1, safe)) 1.360 - bigchunk = string.sub(bigchunk, safe+1) 1.361 - end 1.362 - end 1.363 - return 1.364 - end 1.365 - if not skippart then 1.366 - stream_part_chunk(string.sub(bigchunk, 1, pos1 - 1)) 1.367 - stream_part_finish() 1.368 - else 1.369 - boundary = "\r\n" .. boundary 1.370 - skippart = false 1.371 - end 1.372 - bigchunk = string.sub(bigchunk, pos2 + 1) 1.373 - afterbound = true 1.374 - end 1.375 - end) 1.376 - if not terminated then 1.377 - request_error(true, "400 Bad Request", "Premature end of multipart/form-data request body") 1.378 - end 1.379 - request.post_metadata_list, request.post_metadata = post_metadata_list, post_metadata 1.380 - else 1.381 - request_error(true, "415 Unsupported Media Type", "Unknown Content-Type of request body") 1.382 + local input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384 1.383 + local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024 1.384 + local header_size_limit = options.header_size_limit or 1024*1024 1.385 + local body_size_limit = options.body_size_limit or 64*1024*1024 1.386 + local request_idle_timeout = default("request_idle_timeout", 330) 1.387 + local request_header_timeout = default("request_header_timeout", 30) 1.388 + local request_body_timeout = default("request_body_timeout", 60) 1.389 + local request_response_timeout = default("request_response_timeout", 1800) 1.390 + local poll = options.poll_function or moonbridge_io.poll 1.391 + -- return socket handler: 1.392 + return function(socket) 1.393 + local socket_set = {[socket] = true} -- used for poll function 1.394 + local survive = true -- set to false if process shall be terminated later 1.395 + local consume -- function that reads some input if possible 1.396 + -- function that drains some input if possible: 1.397 + local function drain() 1.398 + local bytes, status = assert(socket:drain_nb(input_chunk_size)) 1.399 + if status == "eof" then 1.400 + consume = nil 1.401 end 1.402 end 1.403 - end 1.404 - self.post_params = get_first_values(self.post_params_list) 1.405 - self._state = "no_status_sent" 1.406 -end 1.407 - 1.408 -function request_pt:_drain_input() 1.409 - self._read_body_coro = "drain" 1.410 -end 1.411 - 1.412 -function request_pt:_consume_some_input() 1.413 - local coro = self._read_body_coro 1.414 - if coro == "drain" then 1.415 - local bytes, status = self._socket:drain_nb(self._input_chunk_size) 1.416 - if status == "eof" then 1.417 - coro = nil 1.418 - end 1.419 - elseif coro then 1.420 - local retval = coro(self) 1.421 - if retval ~= nil then 1.422 - coro = nil -- can't consume more data 1.423 - end 1.424 - end 1.425 -end 1.426 - 1.427 -function request_pt:_consume_all_input() 1.428 - while self._read_body_coro do 1.429 - self._poll(socket_set) 1.430 - self:_consume_some_input() 1.431 - end 1.432 -end 1.433 - 1.434 -function request_pt:_error(status, explanation) 1.435 -end 1.436 - 1.437 -function request_pt:_read(...) 1.438 - local line, status = self._socket:read_yield(...) 1.439 - if line == nil then 1.440 - self._faulty = true 1.441 - error(status) 1.442 - else 1.443 - return line, status 1.444 - end 1.445 -end 1.446 - 1.447 -function request_pt:_read_headers() 1.448 - local remaining = self._header_size_limit 1.449 - -- read and parse request line: 1.450 - local target, proto 1.451 - do 1.452 - local line, status = self:_read(remaining-2, "\n") 1.453 - if status == "maxlen" then 1.454 - self:_error("414 Request-URI Too Long") 1.455 - return false 1.456 - elseif status == "eof" then 1.457 - if line ~= "" then 1.458 - self:_error("400 Bad Request", "Unexpected EOF in request-URI line") 1.459 - end 1.460 - return false 1.461 - end 1.462 - remaining = remaining - #line 1.463 - self.method, target, proto = 1.464 - line:match("^([^ \t\r]+)[ \t]+([^ \t\r]+)[ \t]*([^ \t\r]*)[ \t]*\r?\n$") 1.465 - if not request.method then 1.466 - self:_error("400 Bad Request", "Invalid request-URI line") 1.467 - return false 1.468 - elseif proto ~= "HTTP/1.1" then 1.469 - self:_error("505 HTTP Version Not Supported") 1.470 - return false 1.471 - end 1.472 - end 1.473 - -- read and parse headers: 1.474 - self._headers = {} 1.475 - self._headers_value_nil = {} 1.476 - while true do 1.477 - local line, status = self:_read(remaining, "\n"); 1.478 - if status == "maxlen" then 1.479 - self:_error("431 Request Header Fields Too Large") 1.480 - return false 1.481 - elseif status == "eof" then 1.482 - self:_error("400 Bad Request", "Unexpected EOF in request headers") 1.483 - return false 1.484 - end 1.485 - remaining = remaining - #line 1.486 - if line == "\r\n" or line == "\n" then 1.487 - break 1.488 - end 1.489 - local key, value = string.match(line, "^([^ \t\r]+):[ \t]*(.-)[ \t]*\r?\n$") 1.490 - if not key then 1.491 - self:_error("400 Bad Request", "Invalid header line") 1.492 - return false 1.493 - end 1.494 - local lowerkey = key:lower() 1.495 - local values = self._headers[lowerkey] 1.496 - if values then 1.497 - values[#values+1] = value 1.498 - else 1.499 - self._headers[lowerkey] = {value} 1.500 - end 1.501 - end 1.502 - -- process "Connection: close" header if existent: 1.503 - self._connection_close_requested = self.headers_flags["Connection"]["close"] 1.504 - -- process "Content-Length" header if existent: 1.505 - do 1.506 - local values = self.headers_csv_table["Content-Length"] 1.507 - if #values > 0 then 1.508 - self._request_body_content_length = tonumber(values[1]) 1.509 - local proper_value = tostring(request_body_content_length) 1.510 - for i, value in ipairs(values) do 1.511 - value = string.match(value, "^0*(.*)") 1.512 - if value ~= proper_value then 1.513 - self:_error("400 Bad Request", "Content-Length header(s) invalid") 1.514 - return false 1.515 - end 1.516 - end 1.517 - if request_body_content_length > self._body_size_limit then 1.518 - self:_error("413 Request Entity Too Large", "Announced request body size is too big") 1.519 - return false 1.520 + local function unblock() 1.521 + if consume then 1.522 + poll(socket_set, socket_set) 1.523 + consume() 1.524 + else 1.525 + poll(nil, socket_set) 1.526 end 1.527 end 1.528 - end 1.529 - -- process "Transfer-Encoding" header if existent: 1.530 - do 1.531 - local flag = self.headers_flags["Transfer-Encoding"]["chunked"] 1.532 - local list = self.headers_csv_table["Transfer-Encoding"] 1.533 - if (flag and #list ~= 1) or (not flag and #list ~= 0) then 1.534 - self:_error("400 Bad Request", "Unexpected Transfer-Encoding") 1.535 - return false 1.536 - end 1.537 - end 1.538 - -- process "Expect" header if existent: 1.539 - for i, value in ipairs(self.headers_csv_table["Expect"]) do 1.540 - if string.lower(value) ~= "100-continue" then 1.541 - self:_error("417 Expectation Failed", "Unexpected Expect header") 1.542 - return false 1.543 - end 1.544 - end 1.545 - -- get mandatory Host header according to RFC 7230: 1.546 - self.host = self.headers_value["Host"] 1.547 - if not self.host then 1.548 - self:_error("400 Bad Request", "No valid host header") 1.549 - return false 1.550 - end 1.551 - -- parse request target: 1.552 - self.path, self.query = string.match(target, "^/([^?]*)(.*)$") 1.553 - if not self.path then 1.554 - local host2 1.555 - host2, self.path, self.query = string.match(target, "^[Hh][Tt][Tt][Pp]://([^/?]+)/?([^?]*)(.*)$") 1.556 - if host2 then 1.557 - if self.host ~= host2 then 1.558 - self:_error("400 Bad Request", "No valid host header") 1.559 - return false 1.560 - end 1.561 - elseif not (target == "*" and self.method == "OPTIONS") then 1.562 - self:_error("400 Bad Request", "Invalid request target") 1.563 - return false 1.564 - end 1.565 - end 1.566 - -- parse GET params: 1.567 - if self.query then 1.568 - self.get_params_list = read_urlencoded_form(request.query) 1.569 - self.get_params = get_first_values(self.get_params_list) 1.570 - end 1.571 - -- parse cookies: 1.572 - self.cookies = {} 1.573 - for i, line in ipairs(self.headers["Cookie"]) do 1.574 - for rawkey, rawvalue in 1.575 - string.gmatch(line, "([^=; ]*)=([^=; ]*)") 1.576 - do 1.577 - self.cookies[decode_uri(rawkey)] = decode_uri(rawvalue) 1.578 - end 1.579 - end 1.580 - -- indicate success: 1.581 - return true 1.582 -end 1.583 - 1.584 -function request_pt:_read_body() 1.585 - local remaining = self._body_size_limit 1.586 - if request.headers_flags["Transfer-Encoding"]["chunked"] then 1.587 - while true do 1.588 - local line, status = self:_read(32 + remaining, "\n") 1.589 - if status == "maxlen" then 1.590 - self:_error("400 Bad Request", "Request body size limit exceeded") 1.591 - return false 1.592 - elseif status == "eof" then 1.593 - self:_error("400 Bad Request", "Encoding error or unexpected EOF while reading next chunk of request body") 1.594 - return false 1.595 - end 1.596 - local zeros, lenstr = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)\r?\n$") 1.597 - local chunkext 1.598 - if lenstr then 1.599 - chunkext = "" 1.600 - else 1.601 - zeros, lenstr, chunkext = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)([ \t;].-)\r?\n$") 1.602 - end 1.603 - if not lenstr or #lenstr > 13 then 1.604 - self:_error("400 Bad Request", "Encoding error while reading chunk of request body") 1.605 - return false 1.606 + repeat 1.607 + local request = { 1.608 + socket = socket, 1.609 + cookies = {} 1.610 + } 1.611 + local function send(...) 1.612 + assert(socket:write_call(unblock, ...)) 1.613 end 1.614 - local len = tonumber("0x" .. lenstr) 1.615 - remaining = remaining - (#zeros + #chunkext + len) 1.616 - if remaining < 0 then 1.617 - self:_error("400 Bad Request", "Request body size limit exceeded") 1.618 - return false 1.619 - end 1.620 - if len == 0 then break end 1.621 - if self:_read_body_bytes(len) == false then 1.622 - return false 1.623 - end 1.624 - local term, status = self:_read(2, "\n") 1.625 - if status == "eof" then 1.626 - self:_error("400 Bad Request", "Unexpected EOF while reading next chunk of request body") 1.627 - return false 1.628 - end 1.629 - if term ~= "\r\n" and term ~= "\n" then 1.630 - self:_error("400 Bad Request", "Encoding error while reading chunk of request body") 1.631 - return false 1.632 + -- wait for input: 1.633 + if not poll(socket_set, nil, request_idle_timeout) then 1.634 + -- TODO: send error 1.635 + return survive 1.636 end 1.637 - end 1.638 - while true do 1.639 - local line, status = self:_read(2 + remaining, "\n") 1.640 - if status == "eof" then 1.641 - self:_error("400 Bad Request", "Unexpected EOF while reading chunk of request body") 1.642 - return false 1.643 - end 1.644 - if line == "\r\n" or line == "\n" then break end 1.645 - remaining = remaining - #line 1.646 - if remaining < 0 then 1.647 - self:_error("413 Request Entity Too Large", "Request body size limit exceeded while reading trailer section of chunked request body") 1.648 - return false 1.649 - end 1.650 - end 1.651 - elseif request_body_content_length then 1.652 - if self._read_body_bytes(request_body_content_length) == false then 1.653 - return false 1.654 - end 1.655 + until connection_close_responded 1.656 + return survive 1.657 end 1.658 - -- indicate success: 1.659 - return true 1.660 -end 1.661 - 1.662 -function request_pt:_read_body_bytes(remaining, callback) 1.663 - while remaining > 0 do 1.664 - local limit 1.665 - if remaining > self._input_chunk_size then 1.666 - limit = self._input_chunk_size 1.667 - else 1.668 - limit = remaining 1.669 - end 1.670 - local chunk, status = self:_read(limit) 1.671 - if status == "eof" then 1.672 - self:_error("400 Bad Request", "Unexpected EOF while reading chunk of request body") 1.673 - return false 1.674 - end 1.675 - remaining = remaining - limit 1.676 - if self._body_streamer then 1.677 - self._body_streamer(chunk) 1.678 - end 1.679 - end 1.680 - return true 1.681 -end 1.682 - 1.683 -function request_pt:_assert_not_faulty() 1.684 - assert(not self._faulty, "Tried to use faulty request handle") 1.685 -end 1.686 - 1.687 -function request_pt:_write_yield() 1.688 - self:_consume_some_input() 1.689 - self._poll(self._socket_set, self._socket_set) 1.690 -end 1.691 - 1.692 -function request_pt:_write(...) 1.693 - assert(self._socket:write_call(self._write_yield_closure, ...)) 1.694 -end 1.695 - 1.696 -function request_pt:_flush(...) 1.697 - assert(self._socket:write_call(self._write_yield_closure, ...)) 1.698 -end 1.699 - 1.700 --- function creating a HTTP handler: 1.701 -function generate_handler(handler, options) 1.702 - -- swap arguments if necessary (for convenience): 1.703 - if type(handler) ~= "function" and type(options) == "function" then 1.704 - handler, options = options, handler 1.705 - end 1.706 - local request = setmetatable({}, request_mt) 1.707 - request:_init(handler, options) 1.708 - return request._handler_closure 1.709 end 1.710 1.711 return _M