moonbridge
diff moonbridge_http.lua @ 154:831f2d4b2d73
Initial work on reimplemented HTTP layer (utilizing non-blocking I/O with coroutines and a cleaner object-oriented structure)
author | jbe |
---|---|
date | Thu May 21 02:40:39 2015 +0200 (2015-05-21) |
parents | 7014436d88ea |
children | 2c22b0f222c7 |
line diff
1.1 --- a/moonbridge_http.lua Tue May 19 23:15:22 2015 +0200 1.2 +++ b/moonbridge_http.lua Thu May 21 02:40:39 2015 +0200 1.3 @@ -83,79 +83,37 @@ 1.4 ["304"] = true 1.5 } 1.6 1.7 --- handling of GET/POST param tables: 1.8 -local new_params_list -- defined later 1.9 -do 1.10 - local params_list_mapping = setmetatable({}, {__mode="k"}) 1.11 - local function nextnonempty(tbl, key) 1.12 - while true do 1.13 - key = next(tbl, key) 1.14 - if key == nil then 1.15 - return nil 1.16 - end 1.17 - local value = tbl[key] 1.18 - if #value > 0 then 1.19 - return key, value 1.20 - end 1.21 +-- parses URL encoded form data: 1.22 +local function read_urlencoded_form(data) 1.23 + local tbl = {} 1.24 + for rawkey, rawvalue in string.gmatch(data, "([^?=&]*)=([^?=&]*)") do 1.25 + local key = decode_uri(rawkey) 1.26 + local value = decode_uri(rawvalue) 1.27 + local subtbl = tbl[key] 1.28 + if subtbl then 1.29 + subtbl[#subtbl+1] = value 1.30 + else 1.31 + tbl[key] = {value} 1.32 end 1.33 end 1.34 - local function nextvalue(tbl, key) 1.35 - key = next(tbl, key) 1.36 - if key == nil then 1.37 - return nil 1.38 - end 1.39 - return key, tbl[key][1] 1.40 - end 1.41 - local params_list_metatable = { 1.42 - __index = function(self, key) 1.43 - local tbl = {} 1.44 - self[key] = tbl 1.45 - return tbl 1.46 - end, 1.47 - __pairs = function(self) 1.48 - return nextnonempty, self, nil 1.49 - end 1.50 - } 1.51 - local params_metatable = { 1.52 - __index = function(self, key) 1.53 - return params_list_mapping[self][key][1] 1.54 - end, 1.55 - __newindex = function(self, key, value) 1.56 - params_list_mapping[self][key] = {value} 1.57 - end, 1.58 - __pairs = function(self) 1.59 - return nextvalue, params_list_mapping[self], nil 1.60 - end 1.61 - } 1.62 - -- returns a table to store key value-list pairs (i.e. multiple values), 1.63 - -- and a second table automatically mapping keys to the first value 1.64 - -- using the key value-list pairs in the first table: 1.65 - new_params_list = function() 1.66 - local params_list = setmetatable({}, params_list_metatable) 1.67 - local params = setmetatable({}, params_metatable) 1.68 - params_list_mapping[params] = params_list 1.69 - return params_list, params 1.70 - end 1.71 -end 1.72 --- parses URL encoded form data and stores it in 1.73 --- a key value-list pairs structure that has to be 1.74 --- previously obtained by calling by new_params_list(): 1.75 -local function read_urlencoded_form(tbl, data) 1.76 - for rawkey, rawvalue in string.gmatch(data, "([^?=&]*)=([^?=&]*)") do 1.77 - local subtbl = tbl[decode_uri(rawkey)] 1.78 - subtbl[#subtbl+1] = decode_uri(rawvalue) 1.79 - end 1.80 + return tbl 1.81 end 1.82 1.83 --- function creating a HTTP handler: 1.84 -function generate_handler(handler, options) 1.85 - -- swap arguments if necessary (for convenience): 1.86 - if type(handler) ~= "function" and type(options) == "function" then 1.87 - handler, options = options, handler 1.88 +-- extracts first value from each subtable: 1.89 +local function get_first_values(tbl) 1.90 + local newtbl = {} 1.91 + for key, subtbl in pairs(tbl) do 1.92 + newtbl[key] = subtbl[1] 1.93 end 1.94 + return newtbl 1.95 +end 1.96 + 1.97 +request_pt = {} 1.98 +request_mt = { __index = request_pt } 1.99 + 1.100 +function request_pt:_init(handler, options) 1.101 -- process options: 1.102 options = options or {} 1.103 - local preamble = "" -- preamble sent with every(!) HTTP response 1.104 do 1.105 -- named arg "static_headers" is used to create the preamble: 1.106 local s = options.static_headers 1.107 @@ -176,845 +134,338 @@ 1.108 end 1.109 end 1.110 t[#t+1] = "" 1.111 - preamble = table.concat(t, "\r\n") 1.112 - end 1.113 - local input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384 1.114 - local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024 1.115 - local request_idle_timeout, request_header_timeout, response_timeout 1.116 - if options.request_idle_timeout ~= nil then 1.117 - request_idle_timeout = options.request_idle_timeout or 0 1.118 - else 1.119 - request_idle_timeout = 330 1.120 + self._preamble = table.concat(t, "\r\n") -- preamble sent with every(!) HTTP response 1.121 end 1.122 - if options.request_header_timeout ~= nil then 1.123 - request_header_timeout = options.request_header_timeout or 0 1.124 - else 1.125 - request_header_timeout = 30 1.126 - end 1.127 - if options.timeout ~= nil then 1.128 - response_timeout = options.timeout or 0 1.129 - else 1.130 - response_timeout = 1800 1.131 + self._input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384 1.132 + self._output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024 1.133 + self._header_size_limit = options.header_size_limit or 1024*1024 1.134 + local function init_timeout(name, default) 1.135 + local value = options[name] 1.136 + if value == nil then 1.137 + self["_"..name] = default 1.138 + else 1.139 + self["_"..name] = value or 0 1.140 + end 1.141 end 1.142 - -- return connect handler: 1.143 - return function(socket) 1.144 - local socket_set = {[socket] = true} -- used for moonbridge_io.poll(...) 1.145 - local survive = true -- set to false if process shall be terminated later 1.146 - repeat 1.147 - -- process named arguments "request_header_size_limit" and "request_body_size_limit": 1.148 - local remaining_header_size_limit = options.request_header_size_limit or 1024*1024 1.149 - local remaining_body_size_limit = options.request_body_size_limit or 64*1024*1024 1.150 - -- state variables for request handling: 1.151 - local output_state = "no_status_sent" -- one of: 1.152 - -- "no_status_sent" (initial state) 1.153 - -- "info_status_sent" (1xx status code has been sent) 1.154 - -- "bodyless_status_sent" (204/304 status code has been sent) 1.155 - -- "status_sent" (regular status code has been sent) 1.156 - -- "headers_sent" (headers have been terminated) 1.157 - -- "finished" (request has been answered completely) 1.158 - local content_length -- value of Content-Length header sent 1.159 - local bytes_sent = 0 -- number of bytes sent if Content-Length is set 1.160 - local chunk_parts = {} -- list of chunks to send 1.161 - local chunk_bytes = 0 -- sum of lengths of chunks to send 1.162 - local connection_close_requested = false 1.163 - local connection_close_responded = false 1.164 - local headers_value_nil = {} -- header values that are nil 1.165 - local request_body_content_length -- Content-Length of request body 1.166 - local input_state = "pending" -- one of: 1.167 - -- "pending" (request body has not been processed yet) 1.168 - -- "deferred" (request body processing is deferred) 1.169 - -- "inprogress" (request body is currently being read) 1.170 - -- "finished" (request body has been read) 1.171 - local streamed_post_params = {} -- mapping from POST field name to stream function 1.172 - local streamed_post_param_patterns = {} -- list of POST field pattern and stream function pairs 1.173 - -- object passed to handler (with methods, GET/POST params, etc.): 1.174 - local request 1.175 - -- handling I/O errors (including protocol violations): 1.176 - local socket_closed = false 1.177 - local function assert_output(retval, errmsg) 1.178 - if retval then 1.179 - return retval 1.180 - end 1.181 - request.faulty = true 1.182 - errmsg = "Could not send data to client: " .. errmsg 1.183 - io.stderr:write(errmsg, "\n") 1.184 - if not socket_closed then 1.185 - socket_closed = true 1.186 - socket:reset() 1.187 - end 1.188 - error("Could not send data to client: " .. errmsg) 1.189 + init_timeout("request_idle_timeout", 330) 1.190 + init_timeout("request_header_timeout", 30) 1.191 + init_timeout("request_body_timeout", 1800) 1.192 + init_timeout("response_timeout", 1830) 1.193 + self._poll = options.poll_function or moonbridge_io.poll 1.194 + self:_create_closure("_write_yield") 1.195 + self:_create_closure("_handler") 1.196 + -- table mapping header field names to value-lists: 1.197 + self._headers_mt = { 1.198 + __index = function(tbl, key) 1.199 + local lowerkey = string.lower(key) 1.200 + local result = self._headers[lowerkey] 1.201 + if result == nil then 1.202 + result = {} 1.203 end 1.204 - local function request_error(throw_error, status, text) 1.205 - local errmsg = "Error while reading request from client. Error response: " .. status 1.206 - if text then 1.207 - errmsg = errmsg .. " (" .. text .. ")" 1.208 - end 1.209 - io.stderr:write(errmsg, "\n") -- needs to be written now, because of possible timeout error later 1.210 - if 1.211 - output_state == "no_status_sent" or 1.212 - output_state == "info_status_sent" 1.213 - then 1.214 - local error_response_status, errmsg2 = pcall(function() 1.215 - request:defer_reading() -- don't read request body (because of possibly invalid state) 1.216 - request:close_after_finish() -- send "Connection: close" header 1.217 - request:send_status(status) 1.218 - request:send_header("Content-Type", "text/plain") 1.219 - request:send_data(status, "\n") 1.220 - if text then 1.221 - request:send_data("\n", text, "\n") 1.222 - end 1.223 - request:finish() 1.224 - end) 1.225 - if not error_response_status and not request.faulty then 1.226 - request.faulty = true 1.227 - error("Unexpected error while sending error response: " .. errmsg2) 1.228 - end 1.229 - else 1.230 - if not socket_closed then 1.231 - socket_closed = true 1.232 - socket:reset() 1.233 - end 1.234 - end 1.235 - if throw_error then 1.236 - request.faulty = true 1.237 - error(errmsg) 1.238 - else 1.239 - return survive 1.240 - end 1.241 - end 1.242 - local function assert_not_faulty() 1.243 - assert(not request.faulty, "Tried to use faulty request handle") 1.244 - end 1.245 - -- reads a number of bytes from the socket, 1.246 - -- optionally feeding these bytes chunk-wise 1.247 - -- into a callback function: 1.248 - local function read_body_bytes(remaining, callback) 1.249 - while remaining > 0 do 1.250 - local limit 1.251 - if remaining > input_chunk_size then 1.252 - limit = input_chunk_size 1.253 - else 1.254 - limit = remaining 1.255 - end 1.256 - local chunk = socket:read(limit) 1.257 - if not chunk or #chunk ~= limit then 1.258 - request_error(true, "400 Bad Request", "Unexpected EOF or read error while reading chunk of request body") 1.259 - end 1.260 - remaining = remaining - limit 1.261 - if callback then 1.262 - callback(chunk) 1.263 + tbl[lowerkey] = result 1.264 + tbl[key] = result 1.265 + return result 1.266 + end 1.267 + } 1.268 + -- table mapping header field names to value-lists 1.269 + -- (for headers with comma separated values): 1.270 + self._headers_csv_table_mt = { 1.271 + __index = function(tbl, key) 1.272 + local result = {} 1.273 + for i, line in ipairs(self.headers[key]) do 1.274 + for entry in string.gmatch(line, "[^,]+") do 1.275 + local value = string.match(entry, "^[ \t]*(..-)[ \t]*$") 1.276 + if value then 1.277 + result[#result+1] = value 1.278 end 1.279 end 1.280 end 1.281 - -- flushes or closes the socket (depending on 1.282 - -- whether "Connection: close" header was sent): 1.283 - local function finish_response() 1.284 - if connection_close_responded then 1.285 - -- close output stream: 1.286 - assert_output(socket:finish()) 1.287 - -- wait for EOF of peer to avoid immediate TCP RST condition: 1.288 - do 1.289 - local start_time = moonbridge_io.timeref() 1.290 - repeat 1.291 - socket:drain_nb() 1.292 - local time_left = 2 - moonbridge_io.timeref(start_time) 1.293 - until time_left <= 0 or not moonbridge_io.poll(socket_set, nil, time_left) 1.294 - end 1.295 - -- fully close socket: 1.296 - socket_closed = true -- avoid double close on error 1.297 - assert_output(socket:close()) 1.298 - else 1.299 - assert_output(socket:flush()) 1.300 - request:stream_request_body() 1.301 + tbl[key] = result 1.302 + return result 1.303 + end 1.304 + } 1.305 + -- table mapping header field names to a comma separated string 1.306 + -- (for headers with comma separated values): 1.307 + self._headers_csv_string_mt = { 1.308 + __index = function(tbl, key) 1.309 + local result = {} 1.310 + for i, line in ipairs(self.headers[key]) do 1.311 + result[#result+1] = line 1.312 + end 1.313 + result = string.concat(result, ", ") 1.314 + tbl[key] = result 1.315 + return result 1.316 + end 1.317 + } 1.318 + -- table mapping header field names to a single string value 1.319 + -- (or false if header has been sent multiple times): 1.320 + self._headers_value_mt = { 1.321 + __index = function(tbl, key) 1.322 + if self._headers_value_nil[key] then 1.323 + return nil 1.324 + end 1.325 + local result = nil 1.326 + local values = self.headers_csv_table[key] 1.327 + if #values == 0 then 1.328 + self._headers_value_nil[key] = true 1.329 + elseif #values == 1 then 1.330 + result = values[1] 1.331 + else 1.332 + result = false 1.333 + end 1.334 + tbl[key] = result 1.335 + return result 1.336 + end 1.337 + } 1.338 + -- table mapping header field names to a flag table, 1.339 + -- indicating if the comma separated value contains certain entries: 1.340 + self._headers_flags_mt = { 1.341 + __index = function(tbl, key) 1.342 + local result = setmetatable({}, { 1.343 + __index = function(tbl, key) 1.344 + local lowerkey = string.lower(key) 1.345 + local result = rawget(tbl, lowerkey) or false 1.346 + tbl[lowerkey] = result 1.347 + tbl[key] = result 1.348 + return result 1.349 end 1.350 - end 1.351 - -- writes out buffered chunks (without flushing the socket): 1.352 - local function send_chunk() 1.353 - if chunk_bytes > 0 then 1.354 - assert_output(socket:write(string.format("%x\r\n", chunk_bytes))) 1.355 - for i = 1, #chunk_parts do 1.356 - assert_output(socket:write(chunk_parts[i])) 1.357 - end 1.358 - chunk_parts = {} 1.359 - chunk_bytes = 0 1.360 - assert_output(socket:write("\r\n")) 1.361 - end 1.362 + }) 1.363 + for i, value in ipairs(self.headers_csv_table[key]) do 1.364 + result[string.lower(value)] = true 1.365 end 1.366 - -- terminate header section in response, optionally flushing: 1.367 - -- (may be called multiple times unless response is finished) 1.368 - local function finish_headers(flush) 1.369 - if output_state == "no_status_sent" then 1.370 - error("HTTP status has not been sent yet") 1.371 - elseif output_state == "finished" then 1.372 - error("Response has already been finished") 1.373 - elseif output_state == "info_status_sent" then 1.374 - assert_output(socket:write("\r\n")) 1.375 - assert_output(socket:flush()) 1.376 - output_state = "no_status_sent" 1.377 - elseif output_state == "bodyless_status_sent" then 1.378 - if connection_close_requested and not connection_close_responded then 1.379 - request:send_header("Connection", "close") 1.380 + tbl[key] = result 1.381 + return result 1.382 + end 1.383 + } 1.384 +end 1.385 + 1.386 +function request_pt:_create_closure(name) 1.387 + self[name.."_closure"] = function(...) 1.388 + return self[name](self, ...) 1.389 + end 1.390 +end 1.391 + 1.392 +function request_pt:_create_magictable(name) 1.393 + self[name] = setmetatable({}, self["_"..name.."_mt"]) 1.394 +end 1.395 + 1.396 +function request_pt:_handler(socket) 1.397 + self._socket = socket 1.398 + self._survive = true 1.399 + self._socket_set = {[socket] = true} 1.400 + self._faulty = false 1.401 + self._consume_input = self._drain_input 1.402 + self._headers = {} 1.403 + self._headers_value_nil = {} 1.404 + self:_create_magictable("headers") 1.405 + self:_create_magictable("headers_csv_table") 1.406 + self:_create_magictable("headers_csv_string") 1.407 + self:_create_magictable("headers_value") 1.408 + self:_create_magictable("headers_flags") 1.409 + repeat 1.410 + -- wait for input: 1.411 + if not moonbridge_io.poll(self._socket_set, nil, self._request_idle_timeout) then 1.412 + self:_error("408 Request Timeout", "Idle connection timed out") 1.413 + return self._survive 1.414 + end 1.415 + -- read headers (with timeout): 1.416 + do 1.417 + local coro = coroutine.wrap(self._read_headers) 1.418 + local timeout = self._request_header_timeout 1.419 + local starttime = timeout and moonbridge_io.timeref() 1.420 + while true do 1.421 + local status = coro(self) 1.422 + if status == nil then 1.423 + local remaining 1.424 + if timeout then 1.425 + remaining = timeout - moonbridge_io.timeref(starttime) 1.426 end 1.427 - assert_output(socket:write("\r\n")) 1.428 - finish_response() 1.429 - output_state = "finished" 1.430 - elseif output_state == "status_sent" then 1.431 - if not content_length then 1.432 - assert_output(socket:write("Transfer-Encoding: chunked\r\n")) 1.433 - end 1.434 - if connection_close_requested and not connection_close_responded then 1.435 - request:send_header("Connection", "close") 1.436 + if not self._poll(self._socket_set, nil, remaining) then 1.437 + self:_error("408 Request Timeout", "Timeout while receiving headers") 1.438 + return self._survive 1.439 end 1.440 - assert_output(socket:write("\r\n")) 1.441 - if request.method == "HEAD" then 1.442 - finish_response() 1.443 - elseif flush then 1.444 - assert_output(socket:flush()) 1.445 - end 1.446 - output_state = "headers_sent" 1.447 - elseif output_state ~= "headers_sent" then 1.448 - error("Unexpected internal status in HTTP engine") 1.449 + elseif status == false then 1.450 + return self._survive 1.451 + elseif status == true then 1.452 + break 1.453 + else 1.454 + error("Unexpected yield value") 1.455 end 1.456 end 1.457 - -- create request object and set several functions and values: 1.458 - request = { 1.459 - -- error state: 1.460 - faulty = false, 1.461 - -- allow raw socket access: 1.462 - socket = socket, 1.463 - -- parsed cookies: 1.464 - cookies = {}, 1.465 - -- send a HTTP response status (e.g. "200 OK"): 1.466 - send_status = function(self, value) 1.467 - assert_not_faulty() 1.468 - if input_state == "pending" then 1.469 - request:process_request_body() 1.470 - end 1.471 - if output_state == "info_status_sent" then 1.472 - assert_output(socket:write("\r\n")) 1.473 - assert_output(socket:flush()) 1.474 - elseif output_state ~= "no_status_sent" then 1.475 - error("HTTP status has already been sent") 1.476 - end 1.477 - local status1 = string.sub(value, 1, 1) 1.478 - local status3 = string.sub(value, 1, 3) 1.479 - assert_output(socket:write("HTTP/1.1 ", value, "\r\n", preamble)) 1.480 - local without_response_body = status_without_response_body[status3] 1.481 - if without_response_body then 1.482 - output_state = "bodyless_status_sent" 1.483 - if without_response_body == "zero_content_length" then 1.484 - request:send_header("Content-Length", 0) 1.485 - end 1.486 - elseif status1 == "1" then 1.487 - output_state = "info_status_sent" 1.488 - else 1.489 - output_state = "status_sent" 1.490 - end 1.491 - end, 1.492 - -- send a HTTP response header 1.493 - -- (key and value as separate args): 1.494 - send_header = function(self, key, value) 1.495 - assert_not_faulty() 1.496 - if output_state == "no_status_sent" then 1.497 - error("HTTP status has not been sent yet") 1.498 - elseif 1.499 - output_state ~= "info_status_sent" and 1.500 - output_state ~= "bodyless_status_sent" and 1.501 - output_state ~= "status_sent" 1.502 - then 1.503 - error("All HTTP headers have already been sent") 1.504 - end 1.505 - local key_lower = string.lower(key) 1.506 - if key_lower == "content-length" then 1.507 - if output_state == "info_status_sent" then 1.508 - error("Cannot set Content-Length for informational status response") 1.509 - end 1.510 - local new_content_length = assert(tonumber(value), "Invalid content-length") 1.511 - if content_length == nil then 1.512 - content_length = new_content_length 1.513 - elseif content_length == new_content_length then 1.514 - return 1.515 - else 1.516 - error("Content-Length has been set multiple times with different values") 1.517 - end 1.518 - elseif key_lower == "connection" then 1.519 - for entry in string.gmatch(string.lower(value), "[^,]+") do 1.520 - if string.match(entry, "^[ \t]*close[ \t]*$") then 1.521 - if output_state == "info_status_sent" then 1.522 - error("Cannot set \"Connection: close\" for informational status response") 1.523 - end 1.524 - connection_close_responded = true 1.525 - break 1.526 - end 1.527 - end 1.528 - end 1.529 - assert_output(socket:write(key, ": ", value, "\r\n")) 1.530 - end, 1.531 - -- method to announce (and enforce) connection close after sending the response: 1.532 - close_after_finish = function() 1.533 - assert_not_faulty() 1.534 - if 1.535 - output_state == "headers_sent" or 1.536 - output_state == "finished" 1.537 - then 1.538 - error("All HTTP headers have already been sent") 1.539 - end 1.540 - connection_close_requested = true 1.541 - end, 1.542 - -- method to finish and flush headers: 1.543 - finish_headers = function() 1.544 - assert_not_faulty() 1.545 - finish_headers(true) 1.546 - end, 1.547 - -- send data for response body: 1.548 - send_data = function(self, ...) 1.549 - assert_not_faulty() 1.550 - if output_state == "info_status_sent" then 1.551 - error("No (non-informational) HTTP status has been sent yet") 1.552 - elseif output_state == "bodyless_status_sent" then 1.553 - error("Cannot send response data for body-less status message") 1.554 - end 1.555 - finish_headers(false) 1.556 - if output_state ~= "headers_sent" then 1.557 - error("Unexpected internal status in HTTP engine") 1.558 - end 1.559 - if request.method == "HEAD" then 1.560 - return 1.561 - end 1.562 - for i = 1, select("#", ...) do 1.563 - local str = tostring(select(i, ...)) 1.564 - if #str > 0 then 1.565 - if content_length then 1.566 - local bytes_to_send = #str 1.567 - if bytes_sent + bytes_to_send > content_length then 1.568 - assert_output(socket:write(string.sub(str, 1, content_length - bytes_sent))) 1.569 - bytes_sent = content_length 1.570 - error("Content length exceeded") 1.571 - else 1.572 - assert_output(socket:write(str)) 1.573 - bytes_sent = bytes_sent + bytes_to_send 1.574 - end 1.575 - else 1.576 - chunk_bytes = chunk_bytes + #str 1.577 - chunk_parts[#chunk_parts+1] = str 1.578 - end 1.579 - end 1.580 - end 1.581 - if chunk_bytes >= output_chunk_size then 1.582 - send_chunk() 1.583 - end 1.584 - end, 1.585 - -- flush output buffer: 1.586 - flush = function(self) 1.587 - assert_not_faulty() 1.588 - send_chunk() 1.589 - assert_output(socket:flush()) 1.590 - end, 1.591 - -- finish response: 1.592 - finish = function(self) 1.593 - assert_not_faulty() 1.594 - if output_state == "finished" then 1.595 - return 1.596 - elseif output_state == "info_status_sent" then 1.597 - error("Informational HTTP response can be finished with :finish_headers() method") 1.598 - end 1.599 - finish_headers(false) 1.600 - if output_state == "headers_sent" then 1.601 - if request.method ~= "HEAD" then 1.602 - if content_length then 1.603 - if bytes_sent ~= content_length then 1.604 - error("Content length not used") 1.605 - end 1.606 - else 1.607 - send_chunk() 1.608 - assert_output(socket:write("0\r\n\r\n")) 1.609 - end 1.610 - finish_response() 1.611 - end 1.612 - output_state = "finished" 1.613 - elseif output_state ~= "finished" then 1.614 - error("Unexpected internal status in HTTP engine") 1.615 - end 1.616 - end, 1.617 - -- table mapping header field names to value-lists 1.618 - -- (raw access): 1.619 - headers = setmetatable({}, { 1.620 - __index = function(self, key) 1.621 - local lowerkey = string.lower(key) 1.622 - if lowerkey == key then 1.623 - return 1.624 - end 1.625 - local result = rawget(self, lowerkey) 1.626 - if result == nil then 1.627 - result = {} 1.628 - end 1.629 - self[lowerkey] = result 1.630 - self[key] = result 1.631 - return result 1.632 - end 1.633 - }), 1.634 - -- table mapping header field names to value-lists 1.635 - -- (for headers with comma separated values): 1.636 - headers_csv_table = setmetatable({}, { 1.637 - __index = function(self, key) 1.638 - local result = {} 1.639 - for i, line in ipairs(request.headers[key]) do 1.640 - for entry in string.gmatch(line, "[^,]+") do 1.641 - local value = string.match(entry, "^[ \t]*(..-)[ \t]*$") 1.642 - if value then 1.643 - result[#result+1] = value 1.644 - end 1.645 - end 1.646 - end 1.647 - self[key] = result 1.648 - return result 1.649 - end 1.650 - }), 1.651 - -- table mapping header field names to a comma separated string 1.652 - -- (for headers with comma separated values): 1.653 - headers_csv_string = setmetatable({}, { 1.654 - __index = function(self, key) 1.655 - local result = {} 1.656 - for i, line in ipairs(request.headers[key]) do 1.657 - result[#result+1] = line 1.658 - end 1.659 - result = string.concat(result, ", ") 1.660 - self[key] = result 1.661 - return result 1.662 - end 1.663 - }), 1.664 - -- table mapping header field names to a single string value 1.665 - -- (or false if header has been sent multiple times): 1.666 - headers_value = setmetatable({}, { 1.667 - __index = function(self, key) 1.668 - if headers_value_nil[key] then 1.669 - return nil 1.670 - end 1.671 - local result = nil 1.672 - local values = request.headers_csv_table[key] 1.673 - if #values == 0 then 1.674 - headers_value_nil[key] = true 1.675 - elseif #values == 1 then 1.676 - result = values[1] 1.677 - else 1.678 - result = false 1.679 - end 1.680 - self[key] = result 1.681 - return result 1.682 - end 1.683 - }), 1.684 - -- table mapping header field names to a flag table, 1.685 - -- indicating if the comma separated value contains certain entries: 1.686 - headers_flags = setmetatable({}, { 1.687 - __index = function(self, key) 1.688 - local result = setmetatable({}, { 1.689 - __index = function(self, key) 1.690 - local lowerkey = string.lower(key) 1.691 - local result = rawget(self, lowerkey) or false 1.692 - self[lowerkey] = result 1.693 - self[key] = result 1.694 - return result 1.695 - end 1.696 - }) 1.697 - for i, value in ipairs(request.headers_csv_table[key]) do 1.698 - result[string.lower(value)] = true 1.699 - end 1.700 - self[key] = result 1.701 - return result 1.702 - end 1.703 - }), 1.704 - -- register POST param stream handler for a single field name: 1.705 - stream_post_param = function(self, field_name, callback) 1.706 - assert_not_faulty() 1.707 - if input_state == "inprogress" or input_state == "finished" then 1.708 - error("Cannot register POST param streaming function if request body is already processed") 1.709 - end 1.710 - streamed_post_params[field_name] = callback 1.711 - end, 1.712 - -- register POST param stream handler for a field name pattern: 1.713 - stream_post_params = function(self, pattern, callback) 1.714 - assert_not_faulty() 1.715 - if input_state == "inprogress" or input_state == "finished" then 1.716 - error("Cannot register POST param streaming function if request body is already processed") 1.717 - end 1.718 - streamed_post_param_patterns[#streamed_post_param_patterns+1] = {pattern, callback} 1.719 - end, 1.720 - -- disables automatic request body processing on write 1.721 - -- (use with caution): 1.722 - defer_reading = function(self) 1.723 - assert_not_faulty() 1.724 - if input_state == "pending" then 1.725 - input_state = "deferred" 1.726 - end 1.727 - end, 1.728 - -- processes the request body and sets the request.post_params, 1.729 - -- request.post_params_list, request.meta_post_params, and 1.730 - -- request.meta_post_params_list values (can be called manually or 1.731 - -- automatically if post_params are accessed or data is written out) 1.732 - process_request_body = function(self) 1.733 - assert_not_faulty() 1.734 - if input_state == "finished" then 1.735 - return 1.736 - end 1.737 - local post_params_list, post_params = new_params_list() 1.738 - local content_type = request.headers_value["Content-Type"] 1.739 - if content_type then 1.740 - if 1.741 - content_type == "application/x-www-form-urlencoded" or 1.742 - string.match(content_type, "^application/x%-www%-form%-urlencoded *;") 1.743 - then 1.744 - read_urlencoded_form(post_params_list, request.body) 1.745 - else 1.746 - local boundary = string.match( 1.747 - content_type, 1.748 - '^multipart/form%-data[ \t]*[;,][ \t]*boundary="([^"]+)"$' 1.749 - ) or string.match( 1.750 - content_type, 1.751 - '^multipart/form%-data[ \t]*[;,][ \t]*boundary=([^"; \t]+)$' 1.752 - ) 1.753 - if boundary then 1.754 - local post_metadata_list, post_metadata = new_params_list() 1.755 - boundary = "--" .. boundary 1.756 - local headerdata = "" 1.757 - local streamer 1.758 - local field_name 1.759 - local metadata = {} 1.760 - local value_parts 1.761 - local function default_streamer(chunk) 1.762 - value_parts[#value_parts+1] = chunk 1.763 - end 1.764 - local function stream_part_finish() 1.765 - if streamer == default_streamer then 1.766 - local value = table.concat(value_parts) 1.767 - value_parts = nil 1.768 - if field_name then 1.769 - local values = post_params_list[field_name] 1.770 - values[#values+1] = value 1.771 - local metadata_entries = post_metadata_list[field_name] 1.772 - metadata_entries[#metadata_entries+1] = metadata 1.773 - end 1.774 - else 1.775 - streamer() 1.776 - end 1.777 - headerdata = "" 1.778 - streamer = nil 1.779 - field_name = nil 1.780 - metadata = {} 1.781 - end 1.782 - local function stream_part_chunk(chunk) 1.783 - if streamer then 1.784 - streamer(chunk) 1.785 - else 1.786 - headerdata = headerdata .. chunk 1.787 - while true do 1.788 - local line, remaining = string.match(headerdata, "^(.-)\r?\n(.*)$") 1.789 - if not line then 1.790 - break 1.791 - end 1.792 - if line == "" then 1.793 - streamer = streamed_post_params[field_name] 1.794 - if not streamer then 1.795 - for i, rule in ipairs(streamed_post_param_patterns) do 1.796 - if string.match(field_name, rule[1]) then 1.797 - streamer = rule[2] 1.798 - break 1.799 - end 1.800 - end 1.801 - end 1.802 - if not streamer then 1.803 - value_parts = {} 1.804 - streamer = default_streamer 1.805 - end 1.806 - streamer(remaining, field_name, metadata) 1.807 - return 1.808 - end 1.809 - headerdata = remaining 1.810 - local header_key, header_value = string.match(line, "^([^:]*):[ \t]*(.-)[ \t]*$") 1.811 - if not header_key then 1.812 - request_error(true, "400 Bad Request", "Invalid header in multipart/form-data part") 1.813 - end 1.814 - header_key = string.lower(header_key) 1.815 - if header_key == "content-disposition" then 1.816 - local escaped_header_value = string.gsub(header_value, '"[^"]*"', function(str) 1.817 - return string.gsub(str, "=", "==") 1.818 - end) 1.819 - field_name = string.match(escaped_header_value, ';[ \t]*name="([^"]*)"') 1.820 - if field_name then 1.821 - field_name = string.gsub(field_name, "==", "=") 1.822 - else 1.823 - field_name = string.match(header_value, ';[ \t]*name=([^"; \t]+)') 1.824 - end 1.825 - metadata.file_name = string.match(escaped_header_value, ';[ \t]*filename="([^"]*)"') 1.826 - if metadata.file_name then 1.827 - metadata.file_name = string.gsub(metadata.file_name, "==", "=") 1.828 - else 1.829 - string.match(header_value, ';[ \t]*filename=([^"; \t]+)') 1.830 - end 1.831 - elseif header_key == "content-type" then 1.832 - metadata.content_type = header_value 1.833 - elseif header_key == "content-transfer-encoding" then 1.834 - request_error(true, "400 Bad Request", "Content-transfer-encoding not supported by multipart/form-data parser") 1.835 - end 1.836 - end 1.837 - end 1.838 - end 1.839 - local skippart = true -- ignore data until first boundary 1.840 - local afterbound = false -- interpret 2 bytes after boundary ("\r\n" or "--") 1.841 - local terminated = false -- final boundary read 1.842 - local bigchunk = "" 1.843 - request:stream_request_body(function(chunk) 1.844 - if terminated then 1.845 - return 1.846 - end 1.847 - bigchunk = bigchunk .. chunk 1.848 - while true do 1.849 - if afterbound then 1.850 - if #bigchunk <= 2 then 1.851 - return 1.852 - end 1.853 - local terminator = string.sub(bigchunk, 1, 2) 1.854 - if terminator == "\r\n" then 1.855 - afterbound = false 1.856 - bigchunk = string.sub(bigchunk, 3) 1.857 - elseif terminator == "--" then 1.858 - terminated = true 1.859 - bigchunk = nil 1.860 - return 1.861 - else 1.862 - request_error(true, "400 Bad Request", "Error while parsing multipart body (expected CRLF or double minus)") 1.863 - end 1.864 - end 1.865 - local pos1, pos2 = string.find(bigchunk, boundary, 1, true) 1.866 - if not pos1 then 1.867 - if not skippart then 1.868 - local safe = #bigchunk-#boundary 1.869 - if safe > 0 then 1.870 - stream_part_chunk(string.sub(bigchunk, 1, safe)) 1.871 - bigchunk = string.sub(bigchunk, safe+1) 1.872 - end 1.873 - end 1.874 - return 1.875 - end 1.876 - if not skippart then 1.877 - stream_part_chunk(string.sub(bigchunk, 1, pos1 - 1)) 1.878 - stream_part_finish() 1.879 - else 1.880 - boundary = "\r\n" .. boundary 1.881 - skippart = false 1.882 - end 1.883 - bigchunk = string.sub(bigchunk, pos2 + 1) 1.884 - afterbound = true 1.885 - end 1.886 - end) 1.887 - if not terminated then 1.888 - request_error(true, "400 Bad Request", "Premature end of multipart/form-data request body") 1.889 - end 1.890 - request.post_metadata_list, request.post_metadata = post_metadata_list, post_metadata 1.891 - else 1.892 - request_error(true, "415 Unsupported Media Type", "Unknown Content-Type of request body") 1.893 - end 1.894 - end 1.895 - end 1.896 - request.post_params_list, request.post_params = post_params_list, post_params 1.897 - end, 1.898 - -- stream request body to an (optional) callback function 1.899 - -- without processing it otherwise: 1.900 - stream_request_body = function(self, callback) 1.901 - assert_not_faulty() 1.902 - if input_state ~= "pending" and input_state ~= "deferred" then 1.903 - if callback then 1.904 - if input_state == "inprogress" then 1.905 - error("Request body is currently being processed") 1.906 - else 1.907 - error("Request body has already been processed") 1.908 - end 1.909 - end 1.910 - return 1.911 - end 1.912 - input_state = "inprogress" 1.913 - if request.headers_flags["Expect"]["100-continue"] then 1.914 - request:send_status("100 Continue") 1.915 - request:finish_headers() 1.916 - end 1.917 - if request.headers_flags["Transfer-Encoding"]["chunked"] then 1.918 - while true do 1.919 - local line = socket:read(32 + remaining_body_size_limit, "\n") 1.920 - if not line then 1.921 - request_error(true, "400 Bad Request", "Unexpected EOF while reading next chunk of request body") 1.922 - end 1.923 - local zeros, lenstr = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)\r?\n$") 1.924 - local chunkext 1.925 - if lenstr then 1.926 - chunkext = "" 1.927 - else 1.928 - zeros, lenstr, chunkext = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)([ \t;].-)\r?\n$") 1.929 - end 1.930 - if not lenstr or #lenstr > 13 then 1.931 - request_error(true, "400 Bad Request", "Encoding error or unexpected EOF or read error while reading chunk of request body") 1.932 - end 1.933 - local len = tonumber("0x" .. lenstr) 1.934 - remaining_body_size_limit = remaining_body_size_limit - (#zeros + #chunkext + len) 1.935 - if remaining_body_size_limit < 0 then 1.936 - request_error(true, "413 Request Entity Too Large", "Request body size limit exceeded") 1.937 - end 1.938 - if len == 0 then break end 1.939 - read_body_bytes(len, callback) 1.940 - local term = socket:read(2, "\n") 1.941 - if term ~= "\r\n" and term ~= "\n" then 1.942 - request_error(true, "400 Bad Request", "Encoding error while reading chunk of request body") 1.943 - end 1.944 - end 1.945 - while true do 1.946 - local line = socket:read(2 + remaining_body_size_limit, "\n") 1.947 - if line == "\r\n" or line == "\n" then break end 1.948 - remaining_body_size_limit = remaining_body_size_limit - #line 1.949 - if remaining_body_size_limit < 0 then 1.950 - request_error(true, "413 Request Entity Too Large", "Request body size limit exceeded while reading trailer section of chunked request body") 1.951 - end 1.952 - end 1.953 - elseif request_body_content_length then 1.954 - read_body_bytes(request_body_content_length, callback) 1.955 - end 1.956 - input_state = "finished" 1.957 - end 1.958 - } 1.959 - -- initialize tables for GET params in request object: 1.960 - request.get_params_list, request.get_params = new_params_list() 1.961 - -- add meta table to request object to allow access to "body" and POST params: 1.962 - setmetatable(request, { 1.963 - __index = function(self, key) 1.964 - if key == "body" then 1.965 - local chunks = {} 1.966 - request:stream_request_body(function(chunk) 1.967 - chunks[#chunks+1] = chunk 1.968 - end) 1.969 - self.body = table.concat(chunks) 1.970 - return self.body 1.971 - elseif 1.972 - key == "post_params_list" or key == "post_params" or 1.973 - key == "post_metadata_list" or key == "post_metadata" 1.974 - then 1.975 - request:process_request_body() 1.976 - return request[key] 1.977 - end 1.978 - end 1.979 - }) 1.980 - -- wait for input: 1.981 - if not moonbridge_io.poll({[socket] = true}, nil, request_idle_timeout) then 1.982 - return request_error(false, "408 Request Timeout") 1.983 + end 1.984 + until true 1.985 +end 1.986 + 1.987 +function request_pt:_error(status, explanation) 1.988 +end 1.989 + 1.990 +function request_pt:_read(...) 1.991 + local line, status = self._socket:read_yield(...) 1.992 + if line == nil then 1.993 + self._faulty = true 1.994 + error(status) 1.995 + else 1.996 + return line, status 1.997 + end 1.998 +end 1.999 + 1.1000 +function request_pt:_read_headers() 1.1001 + local remaining = self._header_size_limit 1.1002 + -- read and parse request line: 1.1003 + local target, proto 1.1004 + do 1.1005 + local line, status = self:_read(remaining-2, "\n") 1.1006 + if status == "maxlen" then 1.1007 + self:_error("414 Request-URI Too Long") 1.1008 + return false 1.1009 + elseif status == "eof" then 1.1010 + if line ~= "" then 1.1011 + self:_error("400 Bad Request", "Unexpected EOF in request-URI line") 1.1012 end 1.1013 - -- set timeout for request header processing: 1.1014 - timeout(request_header_timeout) 1.1015 - -- read and parse request line: 1.1016 - local line = socket:read(remaining_header_size_limit, "\n") 1.1017 - if not line then return survive end 1.1018 - remaining_header_size_limit = remaining_header_size_limit - #line 1.1019 - if remaining_header_size_limit == 0 then 1.1020 - return request_error(false, "414 Request-URI Too Long") 1.1021 - end 1.1022 - local target, proto 1.1023 - request.method, target, proto = 1.1024 - line:match("^([^ \t\r]+)[ \t]+([^ \t\r]+)[ \t]*([^ \t\r]*)[ \t]*\r?\n$") 1.1025 - if not request.method then 1.1026 - return request_error(false, "400 Bad Request") 1.1027 - elseif proto ~= "HTTP/1.1" then 1.1028 - return request_error(false, "505 HTTP Version Not Supported") 1.1029 - end 1.1030 - -- read and parse headers: 1.1031 - while true do 1.1032 - local line = socket:read(remaining_header_size_limit, "\n"); 1.1033 - remaining_header_size_limit = remaining_header_size_limit - #line 1.1034 - if not line then 1.1035 - return request_error(false, "400 Bad Request") 1.1036 - end 1.1037 - if line == "\r\n" or line == "\n" then 1.1038 - break 1.1039 - end 1.1040 - if remaining_header_size_limit == 0 then 1.1041 - return request_error(false, "431 Request Header Fields Too Large") 1.1042 - end 1.1043 - local key, value = string.match(line, "^([^ \t\r]+):[ \t]*(.-)[ \t]*\r?\n$") 1.1044 - if not key then 1.1045 - return request_error(false, "400 Bad Request") 1.1046 - end 1.1047 - local values = request.headers[key] 1.1048 - values[#values+1] = value 1.1049 - end 1.1050 - -- process "Connection: close" header if existent: 1.1051 - connection_close_requested = request.headers_flags["Connection"]["close"] 1.1052 - -- process "Content-Length" header if existent: 1.1053 - do 1.1054 - local values = request.headers_csv_table["Content-Length"] 1.1055 - if #values > 0 then 1.1056 - request_body_content_length = tonumber(values[1]) 1.1057 - local proper_value = tostring(request_body_content_length) 1.1058 - for i, value in ipairs(values) do 1.1059 - value = string.match(value, "^0*(.*)") 1.1060 - if value ~= proper_value then 1.1061 - return request_error(false, "400 Bad Request", "Content-Length header(s) invalid") 1.1062 - end 1.1063 - end 1.1064 - if request_body_content_length > remaining_body_size_limit then 1.1065 - return request_error(false, "413 Request Entity Too Large", "Announced request body size is too big") 1.1066 - end 1.1067 + return false 1.1068 + end 1.1069 + remaining = remaining - #line 1.1070 + self.method, target, proto = 1.1071 + line:match("^([^ \t\r]+)[ \t]+([^ \t\r]+)[ \t]*([^ \t\r]*)[ \t]*\r?\n$") 1.1072 + if not request.method then 1.1073 + self:_error("400 Bad Request", "Invalid request-URI line") 1.1074 + return false 1.1075 + elseif proto ~= "HTTP/1.1" then 1.1076 + self:_error("505 HTTP Version Not Supported") 1.1077 + return false 1.1078 + end 1.1079 + end 1.1080 + -- read and parse headers: 1.1081 + while true do 1.1082 + local line, status = self:_read(remaining, "\n"); 1.1083 + if status == "maxlen" then 1.1084 + self:_error("431 Request Header Fields Too Large") 1.1085 + return false 1.1086 + elseif status == "eof" then 1.1087 + self:_error("400 Bad Request", "Unexpected EOF in request headers") 1.1088 + return false 1.1089 + end 1.1090 + remaining = remaining - #line 1.1091 + if line == "\r\n" or line == "\n" then 1.1092 + break 1.1093 + end 1.1094 + local key, value = string.match(line, "^([^ \t\r]+):[ \t]*(.-)[ \t]*\r?\n$") 1.1095 + if not key then 1.1096 + self:_error("400 Bad Request", "Invalid header line") 1.1097 + return false 1.1098 + end 1.1099 + local lowerkey = key:lower() 1.1100 + local values = self._headers[lowerkey] 1.1101 + if values then 1.1102 + values[#values+1] = value 1.1103 + else 1.1104 + self._headers[lowerkey] = {value} 1.1105 + end 1.1106 + end 1.1107 + -- process "Connection: close" header if existent: 1.1108 + self._connection_close_requested = self.headers_flags["Connection"]["close"] 1.1109 + -- process "Content-Length" header if existent: 1.1110 + do 1.1111 + local values = self.headers_csv_table["Content-Length"] 1.1112 + if #values > 0 then 1.1113 + self._request_body_content_length = tonumber(values[1]) 1.1114 + local proper_value = tostring(request_body_content_length) 1.1115 + for i, value in ipairs(values) do 1.1116 + value = string.match(value, "^0*(.*)") 1.1117 + if value ~= proper_value then 1.1118 + self:_error("400 Bad Request", "Content-Length header(s) invalid") 1.1119 + return false 1.1120 end 1.1121 end 1.1122 - -- process "Transfer-Encoding" header if existent: 1.1123 - do 1.1124 - local flag = request.headers_flags["Transfer-Encoding"]["chunked"] 1.1125 - local list = request.headers_csv_table["Transfer-Encoding"] 1.1126 - if (flag and #list ~= 1) or (not flag and #list ~= 0) then 1.1127 - return request_error(false, "400 Bad Request", "Unexpected Transfer-Encoding") 1.1128 - end 1.1129 + if request_body_content_length > self._body_size_limit then 1.1130 + self:_error("413 Request Entity Too Large", "Announced request body size is too big") 1.1131 + return false 1.1132 end 1.1133 - -- process "Expect" header if existent: 1.1134 - for i, value in ipairs(request.headers_csv_table["Expect"]) do 1.1135 - if string.lower(value) ~= "100-continue" then 1.1136 - return request_error(false, "417 Expectation Failed", "Unexpected Expect header") 1.1137 - end 1.1138 - end 1.1139 - -- get mandatory Host header according to RFC 7230: 1.1140 - request.host = request.headers_value["Host"] 1.1141 - if not request.host then 1.1142 - return request_error(false, "400 Bad Request", "No valid host header") 1.1143 + end 1.1144 + end 1.1145 + -- process "Transfer-Encoding" header if existent: 1.1146 + do 1.1147 + local flag = self.headers_flags["Transfer-Encoding"]["chunked"] 1.1148 + local list = self.headers_csv_table["Transfer-Encoding"] 1.1149 + if (flag and #list ~= 1) or (not flag and #list ~= 0) then 1.1150 + self:_error("400 Bad Request", "Unexpected Transfer-Encoding") 1.1151 + return false 1.1152 + end 1.1153 + end 1.1154 + -- process "Expect" header if existent: 1.1155 + for i, value in ipairs(self.headers_csv_table["Expect"]) do 1.1156 + if string.lower(value) ~= "100-continue" then 1.1157 + self:_error("417 Expectation Failed", "Unexpected Expect header") 1.1158 + return false 1.1159 + end 1.1160 + end 1.1161 + -- get mandatory Host header according to RFC 7230: 1.1162 + self.host = self.headers_value["Host"] 1.1163 + if not self.host then 1.1164 + self:_error("400 Bad Request", "No valid host header") 1.1165 + return false 1.1166 + end 1.1167 + -- parse request target: 1.1168 + self.path, self.query = string.match(target, "^/([^?]*)(.*)$") 1.1169 + if not self.path then 1.1170 + local host2 1.1171 + host2, self.path, self.query = string.match(target, "^[Hh][Tt][Tt][Pp]://([^/?]+)/?([^?]*)(.*)$") 1.1172 + if host2 then 1.1173 + if self.host ~= host2 then 1.1174 + self:_error("400 Bad Request", "No valid host header") 1.1175 + return false 1.1176 end 1.1177 - -- parse request target: 1.1178 - request.path, request.query = string.match(target, "^/([^?]*)(.*)$") 1.1179 - if not request.path then 1.1180 - local host2 1.1181 - host2, request.path, request.query = string.match(target, "^[Hh][Tt][Tt][Pp]://([^/?]+)/?([^?]*)(.*)$") 1.1182 - if host2 then 1.1183 - if request.host ~= host2 then 1.1184 - return request_error(false, "400 Bad Request", "No valid host header") 1.1185 - end 1.1186 - elseif not (target == "*" and request.method == "OPTIONS") then 1.1187 - return request_error(false, "400 Bad Request", "Invalid request target") 1.1188 - end 1.1189 - end 1.1190 - -- parse GET params: 1.1191 - if request.query then 1.1192 - read_urlencoded_form(request.get_params_list, request.query) 1.1193 - end 1.1194 - -- parse cookies: 1.1195 - for i, line in ipairs(request.headers["Cookie"]) do 1.1196 - for rawkey, rawvalue in 1.1197 - string.gmatch(line, "([^=; ]*)=([^=; ]*)") 1.1198 - do 1.1199 - request.cookies[decode_uri(rawkey)] = decode_uri(rawvalue) 1.1200 - end 1.1201 - end 1.1202 - -- (re)set timeout for handler: 1.1203 - timeout(response_timeout or 0) 1.1204 - -- call underlying handler and remember boolean result: 1.1205 - if handler(request) ~= true then survive = false end 1.1206 - -- finish request (unless already done by underlying handler): 1.1207 - request:finish() 1.1208 - -- stop timeout timer: 1.1209 - timeout(0) 1.1210 - until connection_close_responded 1.1211 - return survive 1.1212 + elseif not (target == "*" and self.method == "OPTIONS") then 1.1213 + self:_error("400 Bad Request", "Invalid request target") 1.1214 + end 1.1215 + end 1.1216 + -- parse GET params: 1.1217 + if self.query then 1.1218 + self.get_params_list = read_urlencoded_form(request.query) 1.1219 + self.get_params = get_first_values(self.get_params_list) 1.1220 + end 1.1221 + -- parse cookies: 1.1222 + for i, line in ipairs(self.headers["Cookie"]) do 1.1223 + for rawkey, rawvalue in 1.1224 + string.gmatch(line, "([^=; ]*)=([^=; ]*)") 1.1225 + do 1.1226 + self.cookies[decode_uri(rawkey)] = decode_uri(rawvalue) 1.1227 + end 1.1228 end 1.1229 end 1.1230 1.1231 +function request_pt:_assert_not_faulty() 1.1232 + assert(not self._faulty, "Tried to use faulty request handle") 1.1233 +end 1.1234 + 1.1235 +function request_pt:_write_yield() 1.1236 + self:_consume_input() 1.1237 + self._poll(self._socket_set, self._socket_set) 1.1238 +end 1.1239 + 1.1240 +function request_pt:_write(...) 1.1241 + assert(self._socket:write_call(self._write_yield_closure, ...)) 1.1242 +end 1.1243 + 1.1244 +function request_pt:_flush(...) 1.1245 + assert(self._socket:write_call(self._write_yield_closure, ...)) 1.1246 +end 1.1247 + 1.1248 +function request_pt:_drain_input() 1.1249 + socket:drain_nb(self._input_chunk_size) 1.1250 +end 1.1251 + 1.1252 + 1.1253 +-- function creating a HTTP handler: 1.1254 +function generate_handler(handler, options) 1.1255 + -- swap arguments if necessary (for convenience): 1.1256 + if type(handler) ~= "function" and type(options) == "function" then 1.1257 + handler, options = options, handler 1.1258 + end 1.1259 + local request = setmetatable({}, request_mt) 1.1260 + request:_init(handler, options) 1.1261 + return request._handler_closure 1.1262 +end 1.1263 + 1.1264 return _M 1.1265