moonbridge

changeset 173:6e80bcf89bd5

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

Impressum / About Us