moonbridge
changeset 173:6e80bcf89bd5
Added missing header reading/parsing for new HTTP module implementation
author | jbe |
---|---|
date | Wed Jun 17 20:29:44 2015 +0200 (2015-06-17) |
parents | fb54c76e1484 |
children | d6db92e0f231 |
files | moonbridge_http.lua |
line diff
1.1 --- a/moonbridge_http.lua Tue Jun 16 21:44:32 2015 +0200 1.2 +++ b/moonbridge_http.lua Wed Jun 17 20:29:44 2015 +0200 1.3 @@ -200,10 +200,10 @@ 1.4 local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024 1.5 local header_size_limit = options.header_size_limit or 1024*1024 1.6 local body_size_limit = options.body_size_limit or 64*1024*1024 1.7 - local request_idle_timeout = default("request_idle_timeout", 330) 1.8 - local request_header_timeout = default("request_header_timeout", 30) 1.9 - local request_body_timeout = default("request_body_timeout", 60) 1.10 - local request_response_timeout = default("request_response_timeout", 1800) 1.11 + local request_idle_timeout = default("request_idle_timeout", 330) 1.12 + local request_header_timeout = default("request_header_timeout", 30) 1.13 + local request_body_timeout = default("request_body_timeout", 60) 1.14 + local response_timeout = default("response_timeout", 1800) 1.15 local poll = options.poll_function or moonbridge_io.poll 1.16 -- return socket handler: 1.17 return function(socket) 1.18 @@ -446,7 +446,7 @@ 1.19 return survive 1.20 end 1.21 end 1.22 - -- read function 1.23 + -- read functions 1.24 local function read(...) 1.25 local data, status = socket:read_yield(...) 1.26 if data == nil then 1.27 @@ -457,6 +457,20 @@ 1.28 end 1.29 return data 1.30 end 1.31 + local function read_eof(...) 1.32 + local data, status = socket:read_yield(...) 1.33 + if data == nil then 1.34 + request_error(true, "400 Bad Request", "Read error") 1.35 + end 1.36 + if status == "eof" then 1.37 + if data == "" then 1.38 + return nil 1.39 + else 1.40 + request_error(true, "400 Bad Request", "Unexpected EOF") 1.41 + end 1.42 + end 1.43 + return data 1.44 + end 1.45 -- reads a number of bytes from the socket, 1.46 -- optionally feeding these bytes chunk-wise 1.47 -- into a callback function: 1.48 @@ -953,10 +967,142 @@ 1.49 return self[key] 1.50 end 1.51 end 1.52 + -- coroutine for reading headers: 1.53 + local function read_headers() 1.54 + -- read and parse request line: 1.55 + local line = read_eof(remaining_header_size_limit, "\n") 1.56 + if not line then 1.57 + return false, survive 1.58 + end 1.59 + remaining_header_size_limit = remaining_header_size_limit - #line 1.60 + if remaining_header_size_limit == 0 then 1.61 + return false, request_error(false, "414 Request-URI Too Long") 1.62 + end 1.63 + local target, proto 1.64 + request.method, target, proto = 1.65 + line:match("^([^ \t\r]+)[ \t]+([^ \t\r]+)[ \t]*([^ \t\r]*)[ \t]*\r?\n$") 1.66 + if not request.method then 1.67 + return false, request_error(false, "400 Bad Request") 1.68 + elseif proto ~= "HTTP/1.1" then 1.69 + return false, request_error(false, "505 HTTP Version Not Supported") 1.70 + end 1.71 + -- read and parse headers: 1.72 + while true do 1.73 + local line = read(remaining_header_size_limit, "\n"); 1.74 + remaining_header_size_limit = remaining_header_size_limit - #line 1.75 + if line == "\r\n" or line == "\n" then 1.76 + break 1.77 + end 1.78 + if remaining_header_size_limit == 0 then 1.79 + return false, request_error(false, "431 Request Header Fields Too Large") 1.80 + end 1.81 + local key, value = string.match(line, "^([^ \t\r]+):[ \t]*(.-)[ \t]*\r?\n$") 1.82 + if not key then 1.83 + return false, request_error(false, "400 Bad Request") 1.84 + end 1.85 + local values = request.headers[key] 1.86 + values[#values+1] = value 1.87 + end 1.88 + return true -- success 1.89 + end 1.90 -- wait for input: 1.91 if not poll(socket_set, nil, request_idle_timeout) then 1.92 return request_error(false, "408 Request Timeout", "Idle connection timed out") 1.93 end 1.94 + -- read headers (with timeout): 1.95 + do 1.96 + local coro = coroutine.wrap(read_headers) 1.97 + local starttime = request_header_timeout and moonbridge_io.timeref() 1.98 + while true do 1.99 + local status, retval = coro() 1.100 + if status == nil then 1.101 + local remaining 1.102 + if request_header_timeout then 1.103 + remaining = request_header_timeout - moonbridge_io.timeref(starttime) 1.104 + end 1.105 + if not poll(socket_set, nil, remaining) then 1.106 + return request_error(false, "408 Request Timeout", "Timeout while receiving headers") 1.107 + end 1.108 + elseif status == false then 1.109 + return retval 1.110 + elseif status == true then 1.111 + break 1.112 + else 1.113 + error("Unexpected yield value") 1.114 + end 1.115 + end 1.116 + end 1.117 + -- process "Connection: close" header if existent: 1.118 + connection_close_requested = request.headers_flags["Connection"]["close"] 1.119 + -- process "Content-Length" header if existent: 1.120 + do 1.121 + local values = request.headers_csv_table["Content-Length"] 1.122 + if #values > 0 then 1.123 + request_body_content_length = tonumber(values[1]) 1.124 + local proper_value = tostring(request_body_content_length) 1.125 + for i, value in ipairs(values) do 1.126 + value = string.match(value, "^0*(.*)") 1.127 + if value ~= proper_value then 1.128 + return request_error(false, "400 Bad Request", "Content-Length header(s) invalid") 1.129 + end 1.130 + end 1.131 + if request_body_content_length > remaining_body_size_limit then 1.132 + return request_error(false, "413 Request Entity Too Large", "Announced request body size is too big") 1.133 + end 1.134 + end 1.135 + end 1.136 + -- process "Transfer-Encoding" header if existent: 1.137 + do 1.138 + local flag = request.headers_flags["Transfer-Encoding"]["chunked"] 1.139 + local list = request.headers_csv_table["Transfer-Encoding"] 1.140 + if (flag and #list ~= 1) or (not flag and #list ~= 0) then 1.141 + return request_error(false, "400 Bad Request", "Unexpected Transfer-Encoding") 1.142 + end 1.143 + end 1.144 + -- process "Expect" header if existent: 1.145 + for i, value in ipairs(request.headers_csv_table["Expect"]) do 1.146 + if string.lower(value) ~= "100-continue" then 1.147 + return request_error(false, "417 Expectation Failed", "Unexpected Expect header") 1.148 + end 1.149 + end 1.150 + -- get mandatory Host header according to RFC 7230: 1.151 + request.host = request.headers_value["Host"] 1.152 + if not request.host then 1.153 + return request_error(false, "400 Bad Request", "No valid host header") 1.154 + end 1.155 + -- parse request target: 1.156 + request.path, request.query = string.match(target, "^/([^?]*)(.*)$") 1.157 + if not request.path then 1.158 + local host2 1.159 + host2, request.path, request.query = string.match(target, "^[Hh][Tt][Tt][Pp]://([^/?]+)/?([^?]*)(.*)$") 1.160 + if host2 then 1.161 + if request.host ~= host2 then 1.162 + return request_error(false, "400 Bad Request", "No valid host header") 1.163 + end 1.164 + elseif not (target == "*" and request.method == "OPTIONS") then 1.165 + return request_error(false, "400 Bad Request", "Invalid request target") 1.166 + end 1.167 + end 1.168 + -- parse GET params: 1.169 + if request.query then 1.170 + read_urlencoded_form(request.get_params_list, request.query) 1.171 + end 1.172 + -- parse cookies: 1.173 + for i, line in ipairs(request.headers["Cookie"]) do 1.174 + for rawkey, rawvalue in 1.175 + string.gmatch(line, "([^=; ]*)=([^=; ]*)") 1.176 + do 1.177 + request.cookies[decode_uri(rawkey)] = decode_uri(rawvalue) 1.178 + end 1.179 + end 1.180 + -- (re)set timeout for handler: 1.181 + timeout(response_timeout or 0) 1.182 + -- call underlying handler and remember boolean result: 1.183 + if handler(request) ~= true then survive = false end 1.184 + -- finish request (unless already done by underlying handler): 1.185 + request:finish() 1.186 + -- stop timeout timer: 1.187 + timeout(0) 1.188 until close_responded 1.189 return survive 1.190 end