moonbridge

changeset 160:573995950b0b

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

Impressum / About Us