moonbridge

changeset 50:0dd15d642124

Proper handling of I/O errors; Added property "request.faulty"; Removed "io_error_handler" hook; Added documentation for global function "timeout"
author jbe
date Fri Mar 20 02:27:28 2015 +0100 (2015-03-20)
parents 649df11b1f5a
children 2be59069a184
files moonbridge_http.lua reference.txt
line diff
     1.1 --- a/moonbridge_http.lua	Thu Mar 19 22:49:28 2015 +0100
     1.2 +++ b/moonbridge_http.lua	Fri Mar 20 02:27:28 2015 +0100
     1.3 @@ -183,22 +183,6 @@
     1.4    local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024
     1.5    -- return connect handler:
     1.6    return function(socket)
     1.7 -    -- handing I/O errors:
     1.8 -    local socket_closed = false
     1.9 -    local function assert_io(retval, errmsg)
    1.10 -      if retval then
    1.11 -        return retval
    1.12 -      end
    1.13 -      if not socket_closed then
    1.14 -        socket_closed = true
    1.15 -        socket:cancel()
    1.16 -      end
    1.17 -      if options.io_error_handler then
    1.18 -        options.io_error_handler(errmsg)
    1.19 -        error(errmsg)
    1.20 -      end
    1.21 -      error(errmsg, 2)
    1.22 -    end
    1.23      local survive = true  -- set to false if process shall be terminated later
    1.24      repeat
    1.25        -- process named arguments "request_header_size_limit" and "request_body_size_limit":
    1.26 @@ -229,6 +213,57 @@
    1.27        local streamed_post_param_patterns = {}  -- list of POST field pattern and stream function pairs
    1.28        -- object passed to handler (with methods, GET/POST params, etc.):
    1.29        local request
    1.30 +      -- handling I/O errors (including protocol violations):
    1.31 +      local socket_closed = false
    1.32 +      local function assert_output(retval, errmsg)
    1.33 +        if retval then
    1.34 +          return retval
    1.35 +        end
    1.36 +        request.faulty = true
    1.37 +        errmsg = "Could not send data to client: " .. errmsg
    1.38 +        io.stderr:write(errmsg, "\n")
    1.39 +        if not socket_closed then
    1.40 +          socket_closed = true
    1.41 +          socket:cancel()
    1.42 +        end
    1.43 +        error("Could not send data to client: " .. errmsg)
    1.44 +      end
    1.45 +      local function request_error(throw_error, status, text)
    1.46 +        local errmsg = "Error while reading request from client. Error response: " .. status
    1.47 +        if text then
    1.48 +          errmsg = errmsg .. " (" .. text .. ")"
    1.49 +        end
    1.50 +        io.stderr:write(errmsg, "\n")  -- needs to be written now, because of possible timeout error later
    1.51 +        if
    1.52 +          output_state == "no_status_sent" or
    1.53 +          output_state == "info_status_sent"
    1.54 +        then
    1.55 +          local error_response_status, errmsg2 = pcall(function()
    1.56 +            request:defer_reading()  -- don't read request body (because of possibly invalid state)
    1.57 +            request:send_status(status)
    1.58 +            request:send_header("Content-Type", "text/plain")
    1.59 +            request:send_header("Connection", "close")
    1.60 +            request:send_data(status, "\n")
    1.61 +            if text then
    1.62 +              request:send_data("\n", text, "\n")
    1.63 +            end
    1.64 +            request:finish()
    1.65 +          end)
    1.66 +          if not error_response_status and not request.faulty then
    1.67 +            request.faulty = true
    1.68 +            error("Unexpected error while sending error response: " .. errmsg2)
    1.69 +          end
    1.70 +        end
    1.71 +        if throw_error then
    1.72 +          request.faulty = true
    1.73 +          error(errmsg)
    1.74 +        else
    1.75 +          return survive
    1.76 +        end
    1.77 +      end
    1.78 +      local function assert_not_faulty()
    1.79 +        assert(not request.faulty, "Tried to use faulty request handle")
    1.80 +      end
    1.81        -- reads a number of bytes from the socket,
    1.82        -- optionally feeding these bytes chunk-wise
    1.83        -- into a callback function:
    1.84 @@ -242,7 +277,7 @@
    1.85            end
    1.86            local chunk = socket:read(limit)
    1.87            if not chunk or #chunk ~= limit then
    1.88 -            assert_io(false, "Unexpected EOF or read error while reading chunk of request body")
    1.89 +            request_error(true, "400 Bad Request", "Unexpected EOF or read error while reading chunk of request body")
    1.90            end
    1.91            remaining = remaining - limit
    1.92            if callback then
    1.93 @@ -255,29 +290,29 @@
    1.94        local function finish_response()
    1.95          if connection_close_responded then
    1.96            -- close output stream:
    1.97 -          assert_io(socket.output:close())
    1.98 +          assert_output(socket.output:close())
    1.99            -- wait for EOF of peer to avoid immediate TCP RST condition:
   1.100            timeout(2, function()
   1.101              while socket.input:read(input_chunk_size) do end
   1.102            end)
   1.103            -- fully close socket:
   1.104            socket_closed = true  -- avoid double close on error
   1.105 -          assert_io(socket:close())
   1.106 +          assert_output(socket:close())
   1.107          else
   1.108 -          assert_io(socket:flush())
   1.109 +          assert_output(socket:flush())
   1.110            request:stream_request_body()
   1.111          end
   1.112        end
   1.113        -- writes out buffered chunks (without flushing the socket):
   1.114        local function send_chunk()
   1.115          if chunk_bytes > 0 then
   1.116 -          assert_io(socket:write(string.format("%x\r\n", chunk_bytes)))
   1.117 +          assert_output(socket:write(string.format("%x\r\n", chunk_bytes)))
   1.118            for i = 1, #chunk_parts do
   1.119 -            assert_io(socket:write(chunk_parts[i]))
   1.120 +            assert_output(socket:write(chunk_parts[i]))
   1.121            end
   1.122            chunk_parts = {}
   1.123            chunk_bytes = 0
   1.124 -          assert_io(socket:write("\r\n"))
   1.125 +          assert_output(socket:write("\r\n"))
   1.126          end
   1.127        end
   1.128        -- terminate header section in response, optionally flushing:
   1.129 @@ -288,28 +323,28 @@
   1.130          elseif output_state == "finished" then
   1.131            error("Response has already been finished")
   1.132          elseif output_state == "info_status_sent" then
   1.133 -          assert_io(socket:write("\r\n"))
   1.134 -          assert_io(socket:flush())
   1.135 +          assert_output(socket:write("\r\n"))
   1.136 +          assert_output(socket:flush())
   1.137            output_state = "no_status_sent"
   1.138          elseif output_state == "bodyless_status_sent" then
   1.139            if connection_close_requested and not connection_close_responded then
   1.140              request:send_header("Connection", "close")
   1.141            end
   1.142 -          assert_io(socket:write("\r\n"))
   1.143 +          assert_output(socket:write("\r\n"))
   1.144            finish_response()
   1.145            output_state = "finished"
   1.146          elseif output_state == "status_sent" then
   1.147            if not content_length then
   1.148 -            assert_io(socket:write("Transfer-Encoding: chunked\r\n"))
   1.149 +            assert_output(socket:write("Transfer-Encoding: chunked\r\n"))
   1.150            end
   1.151            if connection_close_requested and not connection_close_responded then
   1.152              request:send_header("Connection", "close")
   1.153            end
   1.154 -          assert_io(socket:write("\r\n"))
   1.155 +          assert_output(socket:write("\r\n"))
   1.156            if request.method == "HEAD" then
   1.157              finish_response()
   1.158            elseif flush then
   1.159 -            assert_io(socket:flush())
   1.160 +            assert_output(socket:flush())
   1.161            end
   1.162            output_state = "headers_sent"
   1.163          elseif output_state ~= "headers_sent" then
   1.164 @@ -318,24 +353,27 @@
   1.165        end
   1.166        -- create request object and set several functions and values:
   1.167        request = {
   1.168 +        -- error state:
   1.169 +        faulty = false,
   1.170          -- allow raw socket access:
   1.171          socket = socket,
   1.172          -- parsed cookies:
   1.173          cookies = {},
   1.174          -- send a HTTP response status (e.g. "200 OK"):
   1.175          send_status = function(self, value)
   1.176 +          assert_not_faulty()
   1.177            if input_state == "pending" then
   1.178              request:process_request_body()
   1.179            end
   1.180            if output_state == "info_status_sent" then
   1.181 -            assert_io(socket:write("\r\n"))
   1.182 -            assert_io(socket:flush())
   1.183 +            assert_output(socket:write("\r\n"))
   1.184 +            assert_output(socket:flush())
   1.185            elseif output_state ~= "no_status_sent" then
   1.186              error("HTTP status has already been sent")
   1.187            end
   1.188            local status1 = string.sub(value, 1, 1)
   1.189            local status3 = string.sub(value, 1, 3)
   1.190 -          assert_io(socket:write("HTTP/1.1 ", value, "\r\n", preamble))
   1.191 +          assert_output(socket:write("HTTP/1.1 ", value, "\r\n", preamble))
   1.192            local without_response_body = status_without_response_body[status3]
   1.193            if without_response_body then
   1.194              output_state = "bodyless_status_sent"
   1.195 @@ -351,6 +389,7 @@
   1.196          -- send a HTTP response header
   1.197          -- (key and value as separate args):
   1.198          send_header = function(self, key, value)
   1.199 +          assert_not_faulty()
   1.200            if output_state == "no_status_sent" then
   1.201              error("HTTP status has not been sent yet")
   1.202            elseif
   1.203 @@ -388,14 +427,16 @@
   1.204                end
   1.205              end
   1.206            end
   1.207 -          assert_io(socket:write(key, ": ", value, "\r\n"))
   1.208 +          assert_output(socket:write(key, ": ", value, "\r\n"))
   1.209          end,
   1.210          -- method to finish and flush headers:
   1.211          finish_headers = function()
   1.212 +          assert_not_faulty()
   1.213            finish_headers(true)
   1.214          end,
   1.215          -- send data for response body:
   1.216          send_data = function(self, ...)
   1.217 +          assert_not_faulty()
   1.218            if output_state == "info_status_sent" then
   1.219              error("No (non-informational) HTTP status has been sent yet")
   1.220            elseif output_state == "bodyless_status_sent" then
   1.221 @@ -414,11 +455,11 @@
   1.222                if content_length then
   1.223                  local bytes_to_send = #str
   1.224                  if bytes_sent + bytes_to_send > content_length then
   1.225 -                  assert_io(socket:write(string.sub(str, 1, content_length - bytes_sent)))
   1.226 +                  assert_output(socket:write(string.sub(str, 1, content_length - bytes_sent)))
   1.227                    bytes_sent = content_length
   1.228                    error("Content length exceeded")
   1.229                  else
   1.230 -                  assert_io(socket:write(str))
   1.231 +                  assert_output(socket:write(str))
   1.232                    bytes_sent = bytes_sent + bytes_to_send
   1.233                  end
   1.234                else
   1.235 @@ -433,11 +474,13 @@
   1.236          end,
   1.237          -- flush output buffer:
   1.238          flush = function(self)
   1.239 +          assert_not_faulty()
   1.240            send_chunk()
   1.241 -          assert_io(socket:flush())
   1.242 +          assert_output(socket:flush())
   1.243          end,
   1.244          -- finish response:
   1.245          finish = function(self)
   1.246 +          assert_not_faulty()
   1.247            if output_state == "finished" then
   1.248              return
   1.249            elseif output_state == "info_status_sent" then
   1.250 @@ -452,7 +495,7 @@
   1.251                  end
   1.252                else
   1.253                  send_chunk()
   1.254 -                assert_io(socket:write("0\r\n\r\n"))
   1.255 +                assert_output(socket:write("0\r\n\r\n"))
   1.256                end
   1.257                finish_response()
   1.258              end
   1.259 @@ -550,6 +593,7 @@
   1.260          }),
   1.261          -- register POST param stream handler for a single field name:
   1.262          stream_post_param = function(self, field_name, callback)
   1.263 +          assert_not_faulty()
   1.264            if input_state == "inprogress" or input_state == "finished" then
   1.265              error("Cannot register POST param streaming function if request body is already processed")
   1.266            end
   1.267 @@ -557,6 +601,7 @@
   1.268          end,
   1.269          -- register POST param stream handler for a field name pattern:
   1.270          stream_post_params = function(self, pattern, callback)
   1.271 +          assert_not_faulty()
   1.272            if input_state == "inprogress" or input_state == "finished" then
   1.273              error("Cannot register POST param streaming function if request body is already processed")
   1.274            end
   1.275 @@ -565,6 +610,7 @@
   1.276          -- disables automatic request body processing on write
   1.277          -- (use with caution):
   1.278          defer_reading = function(self)
   1.279 +          assert_not_faulty()
   1.280            if input_state == "pending" then
   1.281              input_state = "deferred"
   1.282            end
   1.283 @@ -574,6 +620,7 @@
   1.284          -- request.meta_post_params_list values (can be called manually or
   1.285          -- automatically if post_params are accessed or data is written out)
   1.286          process_request_body = function(self)
   1.287 +          assert_not_faulty()
   1.288            if input_state == "finished" then
   1.289              return
   1.290            end
   1.291 @@ -652,7 +699,7 @@
   1.292                        headerdata = remaining
   1.293                        local header_key, header_value = string.match(line, "^([^:]*):[ \t]*(.-)[ \t]*$")
   1.294                        if not header_key then
   1.295 -                        assert_io(false, "Invalid header in multipart/form-data part")
   1.296 +                        request_error(true, "400 Bad Request", "Invalid header in multipart/form-data part")
   1.297                        end
   1.298                        header_key = string.lower(header_key)
   1.299                        if header_key == "content-disposition" then
   1.300 @@ -674,7 +721,7 @@
   1.301                        elseif header_key == "content-type" then
   1.302                          metadata.content_type = header_value
   1.303                        elseif header_key == "content-transfer-encoding" then
   1.304 -                        assert_io(false, "Content-transfer-encoding not supported by multipart/form-data parser")
   1.305 +                        request_error(true, "400 Bad Request", "Content-transfer-encoding not supported by multipart/form-data parser")
   1.306                        end
   1.307                      end
   1.308                    end
   1.309 @@ -702,7 +749,7 @@
   1.310                          bigchunk = nil
   1.311                          return
   1.312                        else
   1.313 -                        assert_io(false, "Error while parsing multipart body (expected CRLF or double minus)")
   1.314 +                        request_error(true, "400 Bad Request", "Error while parsing multipart body (expected CRLF or double minus)")
   1.315                        end
   1.316                      end
   1.317                      local pos1, pos2 = string.find(bigchunk, boundary, 1, true)
   1.318 @@ -728,11 +775,11 @@
   1.319                    end
   1.320                  end)
   1.321                  if not terminated then
   1.322 -                  assert_io(false, "Premature end of multipart/form-data request body")
   1.323 +                  request_error(true, "400 Bad Request", "Premature end of multipart/form-data request body")
   1.324                  end
   1.325                  request.post_metadata_list, request.post_metadata = post_metadata_list, post_metadata
   1.326                else
   1.327 -                assert_io(false, "Unknown Content-Type of request body")
   1.328 +                request_error(true, "415 Unsupported Media Type", "Unknown Content-Type of request body")
   1.329                end
   1.330              end
   1.331            end
   1.332 @@ -741,6 +788,7 @@
   1.333          -- stream request body to an (optional) callback function
   1.334          -- without processing it otherwise:
   1.335          stream_request_body = function(self, callback)
   1.336 +          assert_not_faulty()
   1.337            if input_state ~= "pending" and input_state ~= "deferred" then
   1.338              if callback then
   1.339                if input_state == "inprogress" then
   1.340 @@ -760,7 +808,7 @@
   1.341              while true do
   1.342                local line = socket:readuntil("\n", 32 + remaining_body_size_limit)
   1.343                if not line then
   1.344 -                assert_io(false, "Unexpected EOF while reading next chunk of request body")
   1.345 +                request_error(true, "400 Bad Request", "Unexpected EOF while reading next chunk of request body")
   1.346                end
   1.347                local zeros, lenstr = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)\r?\n$")
   1.348                local chunkext
   1.349 @@ -770,18 +818,18 @@
   1.350                  zeros, lenstr, chunkext = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)([ \t;].-)\r?\n$")
   1.351                end
   1.352                if not lenstr or #lenstr > 13 then
   1.353 -                assert_io(false, "Encoding error or unexpected EOF or read error while reading chunk of request body")
   1.354 +                request_error(true, "400 Bad Request", "Encoding error or unexpected EOF or read error while reading chunk of request body")
   1.355                end
   1.356                local len = tonumber("0x" .. lenstr)
   1.357                remaining_body_size_limit = remaining_body_size_limit - (#zeros + #chunkext + len)
   1.358                if remaining_body_size_limit < 0 then
   1.359 -                assert_io(false, "Request body size limit exceeded")
   1.360 +                request_error(true, "413 Request Entity Too Large", "Request body size limit exceeded")
   1.361                end
   1.362                if len == 0 then break end
   1.363                read_body_bytes(len, callback)
   1.364                local term = socket:readuntil("\n", 2)
   1.365                if term ~= "\r\n" and term ~= "\n" then
   1.366 -                assert_io(false, "Encoding error while reading chunk of request body")
   1.367 +                request_error(true, "400 Bad Request", "Encoding error while reading chunk of request body")
   1.368                end
   1.369              end
   1.370              while true do
   1.371 @@ -789,7 +837,7 @@
   1.372                if line == "\r\n" or line == "\n" then break end
   1.373                remaining_body_size_limit = remaining_body_size_limit - #line
   1.374                if remaining_body_size_limit < 0 then
   1.375 -                assert_io(false, "Request body size limit exceeded while reading trailer section of chunked request body")
   1.376 +                request_error(true, "413 Request Entity Too Large", "Request body size limit exceeded while reading trailer section of chunked request body")
   1.377                end
   1.378              end
   1.379            elseif request_body_content_length then
   1.380 @@ -819,51 +867,37 @@
   1.381            end
   1.382          end
   1.383        })
   1.384 -      -- sends a minimalistic error response and enforces closing of the
   1.385 -      -- connection and returns the boolean value "survive"
   1.386 -      local function error_response(status, text)
   1.387 -        request:defer_reading()  -- don't read request body (because of possibly invalid state)
   1.388 -        request:send_status(status)
   1.389 -        request:send_header("Content-Type", "text/plain")
   1.390 -        request:send_header("Connection", "close")
   1.391 -        request:send_data(status, "\n")
   1.392 -        if text then
   1.393 -          request:send_data("\n", text, "\n")
   1.394 -        end
   1.395 -        request:finish()
   1.396 -        return survive
   1.397 -      end
   1.398        -- read and parse request line:
   1.399        local line = socket:readuntil("\n", remaining_header_size_limit)
   1.400        if not line then return survive end
   1.401        remaining_header_size_limit = remaining_header_size_limit - #line
   1.402        if remaining_header_size_limit == 0 then
   1.403 -        return error_response("413 Request Entity Too Large", "Request line too long")
   1.404 +        return request_error(false, "414 Request-URI Too Long")
   1.405        end
   1.406        local target, proto
   1.407        request.method, target, proto =
   1.408          line:match("^([^ \t\r]+)[ \t]+([^ \t\r]+)[ \t]*([^ \t\r]*)[ \t]*\r?\n$")
   1.409        if not request.method then
   1.410 -        return error_response("400 Bad Request")
   1.411 +        return request_error(false, "400 Bad Request")
   1.412        elseif proto ~= "HTTP/1.1" then
   1.413 -        return error_response("505 HTTP Version Not Supported")
   1.414 +        return request_error(false, "505 HTTP Version Not Supported")
   1.415        end
   1.416        -- read and parse headers:
   1.417        while true do
   1.418          local line = socket:readuntil("\n", remaining_header_size_limit);
   1.419          remaining_header_size_limit = remaining_header_size_limit - #line
   1.420          if not line then
   1.421 -          return error_response("400 Bad Request")
   1.422 +          return request_error(false, "400 Bad Request")
   1.423          end
   1.424          if line == "\r\n" or line == "\n" then
   1.425            break
   1.426          end
   1.427          if remaining_header_size_limit == 0 then
   1.428 -          return error_response("413 Request Entity Too Large", "Headers too long")
   1.429 +          return request_error(false, "431 Request Header Fields Too Large")
   1.430          end
   1.431          local key, value = string.match(line, "^([^ \t\r]+):[ \t]*(.-)[ \t]*\r?\n$")
   1.432          if not key then
   1.433 -          return error_response("400 Bad Request")
   1.434 +          return request_error(false, "400 Bad Request")
   1.435          end
   1.436          local values = request.headers[key]
   1.437          values[#values+1] = value
   1.438 @@ -879,11 +913,11 @@
   1.439            for i, value in ipairs(values) do
   1.440              value = string.match(value, "^0*(.*)")
   1.441              if value ~= proper_value then
   1.442 -              return error_response("400 Bad Request", "Content-Length header(s) invalid")
   1.443 +              return request_error(false, "400 Bad Request", "Content-Length header(s) invalid")
   1.444              end
   1.445            end
   1.446            if request_body_content_length > remaining_body_size_limit then
   1.447 -            return error_response("413 Request Entity Too Large", "Request body too big")
   1.448 +            return request_error(false, "413 Request Entity Too Large", "Announced request body size is too big")
   1.449            end
   1.450          end
   1.451        end
   1.452 @@ -892,19 +926,19 @@
   1.453          local flag = request.headers_flags["Transfer-Encoding"]["chunked"]
   1.454          local list = request.headers_csv_table["Transfer-Encoding"]
   1.455          if (flag and #list ~= 1) or (not flag and #list ~= 0) then
   1.456 -          return error_response("400 Bad Request", "Unexpected Transfer-Encoding")
   1.457 +          return request_error(false, "400 Bad Request", "Unexpected Transfer-Encoding")
   1.458          end
   1.459        end
   1.460        -- process "Expect" header if existent:
   1.461        for i, value in ipairs(request.headers_csv_table["Expect"]) do
   1.462          if string.lower(value) ~= "100-continue" then
   1.463 -          return error_response("417 Expectation Failed", "Unexpected Expect header")
   1.464 +          return request_error(false, "417 Expectation Failed", "Unexpected Expect header")
   1.465          end
   1.466        end
   1.467        -- get mandatory Host header according to RFC 7230:
   1.468        request.host = request.headers_value["Host"]
   1.469        if not request.host then
   1.470 -        return error_response("400 Bad Request", "No valid host header")
   1.471 +        return request_error(false, "400 Bad Request", "No valid host header")
   1.472        end
   1.473        -- parse request target:
   1.474        request.path, request.query = string.match(target, "^/([^?]*)(.*)$")
   1.475 @@ -913,10 +947,10 @@
   1.476          host2, request.path, request.query = string.match(target, "^[Hh][Tt][Tt][Pp]://([^/?]+)/?([^?]*)(.*)$")
   1.477          if host2 then
   1.478            if request.host ~= host2 then
   1.479 -            return error_response("400 Bad Request", "No valid host header")
   1.480 +            return request_error(false, "400 Bad Request", "No valid host header")
   1.481            end
   1.482          elseif not (target == "*" and request.method == "OPTIONS") then
   1.483 -          return error_response("400 Bad Request", "Invalid request target")
   1.484 +          return request_error(false, "400 Bad Request", "Invalid request target")
   1.485          end
   1.486        end
   1.487        -- parse GET params:
     2.1 --- a/reference.txt	Thu Mar 19 22:49:28 2015 +0100
     2.2 +++ b/reference.txt	Fri Mar 20 02:27:28 2015 +0100
     2.3 @@ -21,6 +21,26 @@
     2.4  
     2.5  
     2.6  
     2.7 +Global function timeout(...)
     2.8 +----------------------------
     2.9 +
    2.10 +Calling this function with a positive number (time in seconds) sets a timer
    2.11 +that kills the current process after the selected time runs out. The remaining
    2.12 +time can be queried by calling this function without arguments.
    2.13 +
    2.14 +Calling this function with a single argument that is the number zero will
    2.15 +disable the timeout.
    2.16 +
    2.17 +Another mode of operation is selected by passing two arguments: a time (in
    2.18 +seconds) as first argument and a function as second argument. In this case, a
    2.19 +sub-timer will be used to limit the execution time of the function. In case of
    2.20 +timeout, the process will be killed (and the timeout function does not return).
    2.21 +If the time for the sub-timer is longer than a previously set timeout (using
    2.22 +the timeout(...) function with one argument), the shorter timeout (of the
    2.23 +previous call of timeout(...)) will have precedence.
    2.24 +
    2.25 +
    2.26 +
    2.27  Socket object passed to "connect" handler
    2.28  -----------------------------------------
    2.29  
    2.30 @@ -163,8 +183,6 @@
    2.31    ignored when request:flush() is called)
    2.32  - static_headers: a set of headers to be included in every HTTP response
    2.33    (may be a string, a table or strings, or a table of key-value pairs)
    2.34 -- io_error_handler: a function to be called when an I/O operation with the
    2.35 -  client fails (must not return or an error will be raised automatically)
    2.36  
    2.37  The callback function receives a single request object as argument, which is
    2.38  described below.
    2.39 @@ -200,6 +218,14 @@
    2.40  are desired.
    2.41  
    2.42  
    2.43 +### request.faulty
    2.44 +
    2.45 +Normally set to false. In case of a read or write error on the client
    2.46 +connection, this value is set to true before a Lua error is raised.
    2.47 +
    2.48 +A faulty request handle must not be used, or another Lua error will be raised.
    2.49 +
    2.50 +
    2.51  ### request:finish()
    2.52  
    2.53  Finishes and flushes a HTTP response. May be called multiple times. An

Impressum / About Us