moonbridge

changeset 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 08cf8e1865e9
children 2c22b0f222c7
files moonbridge_http.lua
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  

Impressum / About Us