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.
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 currentmain):Server#next_requestturnsProtocol::HTTP1::BadRequestinto a 400 viafail_request, butLineLengthErrorsubclassesProtocol::HTTP1::Errordirectly, so it propagates and the connection closes with zero bytes written (ProtocolErrorbehaves 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
ConfigurableAPI (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::BadRequestmarker inLineLengthError(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.