Class BufferedByteSource

java.lang.Object
java.io.InputStream
dev.relism.flash.transport.BufferedByteSource
All Implemented Interfaces:
Closeable, AutoCloseable

public final class BufferedByteSource extends InputStream
The single buffered view over one connection's inbound bytes, for the whole lifetime of the gives ProtocolNegotiator a way to inspect the first bytes of a plaintext connection (the h2c preface) without consuming them.

Why this exists instead of BufferedInputStream

A generic buffered stream would already fix the per-byte-syscall problem, but it cannot "un-consume" bytes without a fragile mark()/reset() dance, and it has no way to bound an individual read by an absolute wall-clock deadline (see below). This class is purpose-built for exactly the two things this connection loop needs beyond plain buffering: peek(byte[], int, int) (look-ahead without consuming — used once, at connection start, for h2c prior-knowledge detection) and prependOnce(byte[], int, int) (zero-allocation, zero-copy re-insertion of bytes the caller already read into its own buffer — used by ChunkedInputStream to hand back the header-parser's read-ahead bytes instead of the SequenceInputStream/ByteArrayInputStream wrapping this replaces).

Deadline, not SO_TIMEOUT alone

Socket.setSoTimeout(int) bounds a single read() call, not a sequence of them — a peer that trickles one byte every 9 seconds never trips a 10-second SO_TIMEOUT, since each individual read succeeds within the window. setDeadline(long) instead records an absolute System.nanoTime() deadline; every underlying socket read computes the remaining budget and hands exactly that to setSoTimeout before reading, so a SocketTimeoutException from an underlying read unambiguously means the deadline — deadline, do not rely on setSoTimeout alone."

Thread-safety

Not thread-safe, by design — exactly one virtual thread ever owns a connection's inbound bytes at a time (the same invariant RequestParser and ChunkedInputStream already assume).
  • Field Details

    • DEFAULT_BUFFER_SIZE

      public static final int DEFAULT_BUFFER_SIZE
      Default internal buffer size. Matches the relay-buffer convention already used elsewhere in this codebase (the 8 KB STREAM_RELAY_BUFFER in HttpServer) rather than introducing a new tuning constant nothing has calibrated yet.
      See Also:
  • Constructor Details

    • BufferedByteSource

      public BufferedByteSource(InputStream in, Socket socket)
    • BufferedByteSource

      public BufferedByteSource(InputStream in, Socket socket, int bufferSize)
  • Method Details

    • setDeadline

      public void setDeadline(long deadlineNanoTime)
      Every underlying socket read performed after this call is bounded so that it cannot still be blocking past deadlineNanoTime (an absolute value comparable to System.nanoTime()). A read that would exceed the deadline throws SocketTimeoutException instead of blocking further. Bytes already sitting in the internal buffer or the prepend window are served immediately regardless of the deadline — only reads that would otherwise block on the network are bounded.
    • clearDeadline

      public void clearDeadline() throws IOException
      Removes the deadline and restores the socket to blocking indefinitely (SO_TIMEOUT = 0). Must be called before any read the caller wants to be unbounded (e.g. handing the connection off to a long-lived WebSocket session loop). test in this codebase that constructs a BufferedByteSource directly over a ByteArrayInputStream passes null, since there is no real connection to bound) is treated as "no OS-level timeout to clear", not an error — only the deadline bookkeeping is reset. Production always supplies a real socket, so this changes no production behavior; without it, no test can exercise the deadline mechanism at all.
      Throws:
      IOException
    • read

      public int read() throws IOException
      Specified by:
      read in class InputStream
      Throws:
      IOException
    • read

      public int read(byte[] dst, int off, int len) throws IOException
      Overrides:
      read in class InputStream
      Throws:
      IOException
    • skip

      public long skip(long n) throws IOException
      Overrides:
      skip in class InputStream
      Throws:
      IOException
    • available

      public int available()
      Overrides:
      available in class InputStream
    • close

      public void close() throws IOException
      Specified by:
      close in interface AutoCloseable
      Specified by:
      close in interface Closeable
      Overrides:
      close in class InputStream
      Throws:
      IOException
    • peek

      public int peek(byte[] dst, int off, int len) throws IOException
      Ensures up to len bytes are buffered and copies them into dst without advancing the read position — a subsequent read() still returns the same bytes. Blocks (bounded by the active deadline, if any) until len bytes are available or the underlying stream reaches EOF. Returns the number of bytes actually made available, which is less than len only at EOF.

      Only valid before anything has been prepended — in practice this means it is only ever called once, by ProtocolNegotiator, at the very start of a connection before any other read.

      Throws:
      IllegalArgumentException - if len exceeds the internal buffer's capacity — this class cannot peek further ahead than it buffers.
      IOException
    • prependOnce

      public void prependOnce(byte[] src, int off, int len)
      Queues len bytes, starting at off in the caller-owned array src, to be served by the next reads before anything else — zero allocation and zero copy, since src is referenced directly, not duplicated. The caller must not mutate src[off..off+len) until the prefix is fully consumed.

      Exactly one prefix may be pending at a time. This is intentional: it exists solely to hand RequestParser's header-buffer read-ahead bytes to a fresh ChunkedInputStream at the start of a chunked body, a single well-defined moment per request — it is not a general-purpose pushback stack.

      Throws:
      IllegalStateException - if a prefix is already pending