Class RequestParser

java.lang.Object
dev.relism.flash.RequestParser

public class RequestParser extends Object
One instance per connection. The buffer is allocated once and reused across keep-alive requests. Grows on demand (doubling, up to maxHeaderBufferSize).

Zero-allocation design

  • No String allocations during parsing: paths, headers and protocol are exposed as ByteView slices into the shared buffer.
  • headerMap is reset in-place per request — single allocation for the lifetime of the connection.
  • Pipelining / keep-alive leftover bytes are tracked via bufBase and bufLen — no copy between requests on the common path.

State invariant

bufBase and bufLen always reflect unconsumed bytes that belong to the next request. They are snapshotted at the top of parse(dev.relism.flash.transport.BufferedByteSource) and reset to 0/0 before any work begins, so an exception thrown mid-parse leaves the fields clean rather than pointing at stale data from a previous request. Anything wrong with the request itself — smuggling-relevant ambiguity, an over-limit header, a malformed byte where the grammar forbids one — is reported as a MalformedRequestException carrying the exact status the caller must respond with. This is distinct from IOException, which still means "the socket failed" (EOF, reset, timeout). The caller (HttpServer.process) must always close the connection after a MalformedRequestException, never keep it alive — RFC 9112 §6.1's rationale for rejecting Content-Length + Transfer-Encoding outright is exactly that a kept-alive connection after a disputed request boundary is what a smuggling attack needs.
  • Constructor Details

    • RequestParser

      public RequestParser()
    • RequestParser

      public RequestParser(int maxHeaderBufferSize)
    • RequestParser

      public RequestParser(int maxHeaderBufferSize, InetSocketAddress remoteAddress)
    • RequestParser

      public RequestParser(int maxHeaderBufferSize, InetSocketAddress remoteAddress, SSLSocket sslSocket)
      sslSocket is null for a plain connection — see Request.isSecure().
  • Method Details

    • hasBufferedBytes

      public boolean hasBufferedBytes()
      Whether bytes from a previous parse(dev.relism.flash.transport.BufferedByteSource) call are already buffered and ready to be consumed by the next call without reading anything further from the source — the HTTP pipelining case. The caller (the connection loop) uses this to decide whether it is safe to skip waiting for "the next request has started arriving": if bytes are already buffered, the next request has, by definition, already started (and may even be complete), so an idle-timeout wait on the underlying source would wait for bytes that were never going to arrive there — they are already here.
    • parse

      public Request parse(BufferedByteSource in) throws IOException
      Parses the next HTTP request from in.

      Exception safety: bufBase and bufLen are reset to 0 before any parsing work begins. If an exception is thrown, the connection will be closed by the caller, so stale leftover state is never a problem — but the reset ensures correctness in test scenarios where the same parser instance is reused after an error.

      Returns:
      the parsed Request, or null on clean EOF.
      Throws:
      MalformedRequestException - if the request violates the HTTP/1.1 grammar or a configured safety limit — carries the exact status to respond with.
      IOException - on genuine I/O failure (socket reset, timeout).