java.lang.Object
java.io.OutputStream
java.io.FilterOutputStream
net.i2p.i2ptunnel.HTTPResponseOutputStream
All Implemented Interfaces:
Closeable, Flushable, AutoCloseable
Direct Known Subclasses:
I2PTunnelHTTPServer.CompressedResponseOutputStream

class HTTPResponseOutputStreamextends FilterOutputStream
HTTP response stream with transparent gzip decompression (client side).

Extended in I2PTunnelHTTPServer for server-side compression. Simple filtered response stream ensuring "Connection: close" header. Could handle Content-Encoding: x-i2p-gzip transparently, adjusting to Content-Encoding: identity. gzip encoding trivial, but chunked transfer encoding adds complexity.

  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    private long
    Entity-body bytes delivered after the first header block (Range resume offset).
    private final byte[]
    Buffer for single byte writes.
    private static final ByteCache
    Cache supplying the write buffers.
    Callback invoked when the stream finishes.
    private boolean
    True if the response body is chunked, set during header parsing.
    private boolean
    True once the Connection header has been sent, set during header parsing.
    protected String
    Response Content-Encoding header value, lower-case and trimmed.
    protected String
    Response Content-Type header value, lower-case and trimmed.
    protected long
    Content-Length of the body in bytes, -1 if unknown or chunked, 0 for a bodyless response.
    protected boolean
    Set when the response carries Content-Encoding: x-i2p-gzip, so the body is gunzipped.
    protected ByteArray
    Pooled buffer holding the response header block while it is parsed, null once emitted.
    private boolean
    True once the header was written.
    protected boolean
    True if the outproxy (I2P) side connection may be reused after this response.
    protected boolean
    True if the browser side socket may be reused after this response.
    private final Log
    Logger for the HTTP response stream.
    private long
    Body bytes to drop from the next response before forwarding (200 after Range).
    private long
    Content-Length from the first response; preserved across a resume header parse.
    private int
    HTTP status of the response currently being parsed.
    private boolean
    Swallow the next header block (mid-body Range resume); do not emit to the browser.
    private boolean
    True when a transient status (408/502/503/504) aborted a Range resume — next attempt must re-request from byte 0 without Range.
    private static final int
    Size of the cached buffers.
    private static final byte[]
    The Connection close header line.
    private static final byte[]
    The bare CRLF line.
    private static final int
    Largest header buffered before sending, to stop an OOM denial of service.
    private static final byte
    We ignore any potential \r, since we trim it on write anyway.

    Fields inherited from class FilterOutputStream

    out
  • Constructor Summary

    Modifier
    Constructor
    Description
    Construct a response filter with no callback and no keepalive.
    HTTPResponseOutputStream(OutputStream raw, boolean allowKeepAliveIn, boolean allowKeepAliveOut, boolean isHead, LimitOutputStream.DoneCallback cb)
    Optionally keep sockets alive and call callback when we're done.
    private
    Optionally call callback when we're done.
  • Method Summary

    Modifier and Type
    Method
    Description
    protected void
    Start decoding the body, wrapping the output so x-i2p-gzip data is gunzipped.
    boolean
    Whether a Content-Length body can be safely spliced with Range resume: headers written, a positive Content-Length, some body still missing, and no transparent gzip decode (which would desync the Range offset from wire bytes).
    void
    Clear the transient-failure flag after the resume loop has observed it (budget refund / non-Range fallback decided).
    void
    Release the pooled header buffer and close the underlying stream.
    private void
    Grow (and free) the header buffer as necessary.
    private void
    Disable keepalive where the data length cannot be determined, so the end of the response would be ambiguous.
    protected void
    Terminate the header block by writing the blank line that ends it.
    private void
    Complete a suppressed (resume) header block: restore the original Content-Length, decide how many body bytes to skip, and free the header buffer.
    long
    Entity-body bytes forwarded to the browser after the first headers.
    long
    Original response Content-Length, or -1 if unknown/chunked/HEAD-empty.
    boolean
    Whether the first response header block has been written to the browser.
    boolean
    Should we keep the input stream alive when done?
    boolean
    Should we keep the output stream alive when done? Only supported for the browser socket side.
    private void
    handleHeaderLine(byte[] data, int start, int end)
    Parse and write out one header line, stripping hop-by-hop headers and saving the fields needed for the keepalive and compress decisions.
    private void
    handleResponseLine(byte[] data, int len, int valid)
    Write the first line of the response, validating it is an HTTP/x.x response and disabling persistence for non-1.1 or zero-data responses.
    private boolean
    Are the headers finished?
    boolean
    True when a transient status (408/502/503/504) aborted a Range resume.
    private void
    Split the header buffer into lines and process each one.
    void
    Enter mid-body resume mode: the next header block parsed from a fresh tunnel is consumed for bookkeeping only and must not be written to the browser (the original status line and Content-Length were already sent).
    private void
    setupStreams(boolean shouldCompress)
    Set up the filter streams to count the data or look for the end of a chunked transfer, so the callback fires when the response is done.
    protected boolean
    Whether the response body should be compressed before it is forwarded.
    void
    write(byte[] buf, int off, int len)
    Buffer bytes into the header block until the blank line arrives, then write the header out and forward the remaining bytes as the entity body.
    void
    write(int c)
    Write a single byte, buffering it until the header block is complete.
    private void
    writeBody(byte[] buf, int off, int len)
    Forward entity-body bytes, applying any Range-resume skip of a full-body (200) re-send that duplicates an already-delivered prefix.
    private void
    Parse the received header, adjust it as needed, and write it to the client.

    Methods inherited from class FilterOutputStream

    flush, write

    Methods inherited from class OutputStream

    nullOutputStream

    Methods inherited from class Object

    clone, equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • _log

      private final Log _log
      Logger for the HTTP response stream.
    • _headerBuffer

      protected ByteArray _headerBuffer
      Pooled buffer holding the response header block while it is parsed, null once emitted.
    • _headerWritten

      private volatile boolean _headerWritten
      True once the header was written.
    • _buf1

      private final byte[] _buf1
      Buffer for single byte writes.
    • _gzip

      protected volatile boolean _gzip
      Set when the response carries Content-Encoding: x-i2p-gzip, so the body is gunzipped.
    • _dataExpected

      protected volatile long _dataExpected
      Content-Length of the body in bytes, -1 if unknown or chunked, 0 for a bodyless response.
    • _keepAliveIn

      protected volatile boolean _keepAliveIn
      True if the outproxy (I2P) side connection may be reused after this response.
    • _keepAliveOut

      protected volatile boolean _keepAliveOut
      True if the browser side socket may be reused after this response.
    • _chunked

      private boolean _chunked
      True if the response body is chunked, set during header parsing.
    • _connectionSent

      private boolean _connectionSent
      True once the Connection header has been sent, set during header parsing.
    • _contentType

      protected String _contentType
      Response Content-Type header value, lower-case and trimmed.
    • _contentEncoding

      protected String _contentEncoding
      Response Content-Encoding header value, lower-case and trimmed.
    • _callback

      private final LimitOutputStream.DoneCallback _callback
      Callback invoked when the stream finishes.
    • _bodyReceived

      private volatile long _bodyReceived
      Entity-body bytes delivered after the first header block (Range resume offset).
    • _suppressHeaderOutput

      private volatile boolean _suppressHeaderOutput
      Swallow the next header block (mid-body Range resume); do not emit to the browser.
    • _savedDataExpected

      private long _savedDataExpected
      Content-Length from the first response; preserved across a resume header parse.
    • _statusCode

      private int _statusCode
      HTTP status of the response currently being parsed.
    • _resumeSkipBytes

      private long _resumeSkipBytes
      Body bytes to drop from the next response before forwarding (200 after Range).
    • _transientResumeFailure

      private volatile boolean _transientResumeFailure
      True when a transient status (408/502/503/504) aborted a Range resume — next attempt must re-request from byte 0 without Range.
    • CACHE_SIZE

      private static final int CACHE_SIZE
      Size of the cached buffers.
      See Also:
    • _cache

      private static final ByteCache _cache
      Cache supplying the write buffers.
    • MAX_HEADER_SIZE

      private static final int MAX_HEADER_SIZE
      Largest header buffered before sending, to stop an OOM denial of service.
      See Also:
    • NL

      private static final byte NL
      We ignore any potential \r, since we trim it on write anyway.
      See Also:
    • CONNECTION_CLOSE

      private static final byte[] CONNECTION_CLOSE
      The Connection close header line.
    • CRLF

      private static final byte[] CRLF
      The bare CRLF line.
  • Constructor Details

    • HTTPResponseOutputStream

      public HTTPResponseOutputStream(OutputStream raw)
      Construct a response filter with no callback and no keepalive.
      Parameters:
      raw - the stream the filtered response is written to
    • HTTPResponseOutputStream

      private HTTPResponseOutputStream(OutputStream raw, LimitOutputStream.DoneCallback cb)
      Optionally call callback when we're done.
      Parameters:
      raw - the underlying output stream
      cb - may be null
    • HTTPResponseOutputStream

      public HTTPResponseOutputStream(OutputStream raw, boolean allowKeepAliveIn, boolean allowKeepAliveOut, boolean isHead, LimitOutputStream.DoneCallback cb)
      Optionally keep sockets alive and call callback when we're done.
      Parameters:
      raw - the socket stream that the response headers and body are written to
      allowKeepAliveIn - We may, but are not required to, keep the input socket alive. This is the server on the server side and I2P on the client side.
      allowKeepAliveOut - We may, but are not required to, keep the output socket alive. This is I2P on the server side and the browser on the client side.
      isHead - is this a response to a HEAD, and thus no data is expected (RFC 2616 sec. 4.4)
      cb - non-null if allowKeepAlive is true
  • Method Details

    • getKeepAliveIn

      public boolean getKeepAliveIn()
      Should we keep the input stream alive when done?
      Returns:
      false before the headers are written
    • getKeepAliveOut

      public boolean getKeepAliveOut()
      Should we keep the output stream alive when done? Only supported for the browser socket side. I2P socket on server side not supported yet.
      Returns:
      false before the headers are written
    • getHeaderWritten

      public boolean getHeaderWritten()
      Whether the first response header block has been written to the browser.
      Returns:
      true once response headers have been emitted
    • getDataExpected

      public long getDataExpected()
      Original response Content-Length, or -1 if unknown/chunked/HEAD-empty. Preserved across a mid-body Range resume so completion checks stay stable when the second response reports a shorter (206) length.
      Returns:
      entity length in bytes, or -1 if not set
    • getBodyReceived

      public long getBodyReceived()
      Entity-body bytes forwarded to the browser after the first headers. This is the HTTP Range start offset for a mid-body resume.
      Returns:
      body bytes delivered; 0 before any body byte
    • canRangeResume

      public boolean canRangeResume()
      Whether a Content-Length body can be safely spliced with Range resume: headers written, a positive Content-Length, some body still missing, and no transparent gzip decode (which would desync the Range offset from wire bytes).
      Returns:
      true if prepareBodyResume() is safe to attempt
    • isTransientResumeFailure

      public boolean isTransientResumeFailure()
      True when a transient status (408/502/503/504) aborted a Range resume. The resume loop should fall back to a non-Range reconnect and refund the ResumeBudget charge for this attempt.
      Returns:
      true if the last resume failed with a transient status
    • clearTransientResumeFailure

      public void clearTransientResumeFailure()
      Clear the transient-failure flag after the resume loop has observed it (budget refund / non-Range fallback decided).
    • prepareBodyResume

      public void prepareBodyResume()
      Enter mid-body resume mode: the next header block parsed from a fresh tunnel is consumed for bookkeeping only and must not be written to the browser (the original status line and Content-Length were already sent).
    • write

      public void write(int c) throws IOException
      Write a single byte, buffering it until the header block is complete.
      Overrides:
      write in class FilterOutputStream
      Throws:
      IOException
    • write

      public void write(byte[] buf, int off, int len) throws IOException
      Buffer bytes into the header block until the blank line arrives, then write the header out and forward the remaining bytes as the entity body.
      Overrides:
      write in class FilterOutputStream
      Parameters:
      buf - source buffer holding header bytes, then body bytes
      off - index in buf of the first byte to write
      len - number of bytes to write from buf
      Throws:
      IOException - if the header exceeds the size limit or the underlying write fails
    • writeBody

      private void writeBody(byte[] buf, int off, int len) throws IOException
      Forward entity-body bytes, applying any Range-resume skip of a full-body (200) re-send that duplicates an already-delivered prefix.
      Parameters:
      buf - buffer holding the bytes already read from the peer
      off - index of the first body byte within buf
      len - number of body bytes available in buf from off
      Throws:
      IOException - if the browser write fails
    • ensureCapacity

      private void ensureCapacity() throws IOException
      Grow (and free) the header buffer as necessary.
      Throws:
      IOException - if the headers are too big
    • headerReceived

      private boolean headerReceived()
      Are the headers finished?
      Returns:
      true if the buffered data ends with a blank line, \n\r\n or \n\n, so the response headers are complete
    • writeHeader

      private void writeHeader() throws IOException
      Parse the received header, adjust it as needed, and write it to the client.
      Throws:
      IOException - if a suppressed resume response cannot be spliced, or writing to the client fails
    • finishResumeHeaders

      private void finishResumeHeaders() throws IOException
      Complete a suppressed (resume) header block: restore the original Content-Length, decide how many body bytes to skip, and free the header buffer. Throws if the second response cannot be spliced.
      Throws:
      IOException - if the status is not 200/206 or the encoding changed
    • parseHeaders

      private void parseHeaders() throws IOException
      Split the header buffer into lines and process each one. Sets the response-scoped _chunked and _connectionSent flags.
      Throws:
      IOException - on malformed headers
    • handleResponseLine

      private void handleResponseLine(byte[] data, int len, int valid) throws IOException
      Write the first line of the response, validating it is an HTTP/x.x response and disabling persistence for non-1.1 or zero-data responses.
      Parameters:
      data - the header buffer
      len - the length of the response line, including the NL
      valid - the total number of valid bytes in the buffer
      Throws:
      IOException - on a corrupt response line
    • handleHeaderLine

      private void handleHeaderLine(byte[] data, int start, int end) throws IOException
      Parse and write out one header line, stripping hop-by-hop headers and saving the fields needed for the keepalive and compress decisions.
      Parameters:
      data - the header buffer
      start - the first byte of the header
      end - one past the last byte of the header (the NL)
      Throws:
      IOException - on a malformed header
    • finalizeKeepAlive

      private void finalizeKeepAlive()
      Disable keepalive where the data length cannot be determined, so the end of the response would be ambiguous.
    • setupStreams

      private void setupStreams(boolean shouldCompress)
      Set up the filter streams to count the data or look for the end of a chunked transfer, so the callback fires when the response is done.
      Parameters:
      shouldCompress - whether the data will be gzip-compressed
    • shouldCompress

      protected boolean shouldCompress()
      Whether the response body should be compressed before it is forwarded.
      Returns:
      true if the body should be compressed
    • finishHeaders

      protected void finishHeaders() throws IOException
      Terminate the header block by writing the blank line that ends it.
      Throws:
      IOException - if the underlying stream cannot be written
    • close

      public void close() throws IOException
      Release the pooled header buffer and close the underlying stream.
      Specified by:
      close in interface AutoCloseable
      Specified by:
      close in interface Closeable
      Overrides:
      close in class FilterOutputStream
      Throws:
      IOException
    • beginProcessing

      protected void beginProcessing() throws IOException
      Start decoding the body, wrapping the output so x-i2p-gzip data is gunzipped.
      Throws:
      IOException - if the gunzip wrapper cannot be created