Skip to content

HTTP/1 server closes the connection without a response when a line exceeds maximum_line_length #237

Description

@trevorturk

When a request line (or header line) exceeds maximum_line_length, the HTTP/1 server closes the connection without writing anything (async-http 0.98.1, protocol-http1 0.39.0, same on current main):

require "async"
require "async/http/server"
require "async/http/endpoint"

endpoint = Async::HTTP::Endpoint.parse("http://127.0.0.1:9292")
app = proc { |request| Protocol::HTTP::Response[200, {}, ["ok"]] }

Sync do |task|
  task.async { Async::HTTP::Server.new(app, endpoint).run }
  task.sleep
end
$ curl "http://127.0.0.1:9292/?q=$(printf 'a%.0s' {1..10000})"
curl: (52) Empty reply from server

Server#next_request turns Protocol::HTTP1::BadRequest into a 400 via fail_request, but LineLengthError subclasses Protocol::HTTP1::Error directly, so it propagates and the connection closes with zero bytes written (ProtocolError behaves the same way). RFC 9112 §3 suggests a 414 for an over-long request-target, and §5.4 an appropriate 4xx for oversized fields.

We noticed because scanner bots with very long probe URLs show up proxy-side as "backend closed connection without response" platform errors, which look like app faults. Raising the limit via the Configurable API (thanks for socketry/protocol-http1#41 / #198!) already solved that for us — this question is only about the no-response behavior at whatever limit is set.

Is the silent close intentional? The limit exists for DoS protection, so I can see the case for not spending bytes on an abusive peer. If you'd take a change, I'd be happy to send a PR in the spirit of #129: the peer gets a well-formed response instead of an unexplained EOF, and the error still raises exactly as it does today. Since the request-line and header-line cases can't be distinguished at the rescue site, the options seem to be including the Protocol::HTTP::BadRequest marker in LineLengthError (existing 400 path catches it), or splitting the error by phase in protocol-http1 and mapping 414/431. Happy to go whichever direction you prefer — or to close this if it's working as intended!

Co-authored with Claude.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions