java.lang.Object
java.io.OutputStream
java.io.FilterOutputStream
net.i2p.i2ptunnel.HTTPResponseOutputStream
- All Implemented Interfaces:
Closeable, Flushable, AutoCloseable
- Direct Known Subclasses:
I2PTunnelHTTPServer.CompressedResponseOutputStream
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
FieldsModifier and TypeFieldDescriptionprivate longEntity-body bytes delivered after the first header block (Range resume offset).private final byte[]Buffer for single byte writes.private static final ByteCacheCache supplying the write buffers.private final LimitOutputStream.DoneCallbackCallback invoked when the stream finishes.private booleanTrue if the response body is chunked, set during header parsing.private booleanTrue once the Connection header has been sent, set during header parsing.protected StringResponse Content-Encoding header value, lower-case and trimmed.protected StringResponse Content-Type header value, lower-case and trimmed.protected longContent-Length of the body in bytes, -1 if unknown or chunked, 0 for a bodyless response.protected booleanSet when the response carries Content-Encoding: x-i2p-gzip, so the body is gunzipped.protected ByteArrayPooled buffer holding the response header block while it is parsed, null once emitted.private booleanTrue once the header was written.protected booleanTrue if the outproxy (I2P) side connection may be reused after this response.protected booleanTrue if the browser side socket may be reused after this response.private final LogLogger for the HTTP response stream.private longBody bytes to drop from the next response before forwarding (200 after Range).private longContent-Length from the first response; preserved across a resume header parse.private intHTTP status of the response currently being parsed.private booleanSwallow the next header block (mid-body Range resume); do not emit to the browser.private booleanTrue 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 intSize of the cached buffers.private static final byte[]The Connection close header line.private static final byte[]The bare CRLF line.private static final intLargest header buffered before sending, to stop an OOM denial of service.private static final byteWe ignore any potential \r, since we trim it on write anyway.Fields inherited from class FilterOutputStream
outConstructor Summary
ModifierConstructorDescriptionConstruct 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.privateOptionally call callback when we're done.Method Summary
Modifier and TypeMethodDescriptionprotected voidStart decoding the body, wrapping the output so x-i2p-gzip data is gunzipped.booleanWhether 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).voidClear the transient-failure flag after the resume loop has observed it (budget refund / non-Range fallback decided).voidclose()Release the pooled header buffer and close the underlying stream.private voidGrow (and free) the header buffer as necessary.private voidDisable keepalive where the data length cannot be determined, so the end of the response would be ambiguous.protected voidTerminate the header block by writing the blank line that ends it.private voidComplete a suppressed (resume) header block: restore the original Content-Length, decide how many body bytes to skip, and free the header buffer.longEntity-body bytes forwarded to the browser after the first headers.longOriginal response Content-Length, or -1 if unknown/chunked/HEAD-empty.booleanWhether the first response header block has been written to the browser.booleanShould we keep the input stream alive when done?booleanShould we keep the output stream alive when done? Only supported for the browser socket side.private voidhandleHeaderLine(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 voidhandleResponseLine(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 booleanAre the headers finished?booleanTrue when a transient status (408/502/503/504) aborted a Range resume.private voidSplit the header buffer into lines and process each one.voidEnter 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 voidsetupStreams(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 booleanWhether the response body should be compressed before it is forwarded.voidwrite(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.voidwrite(int c) Write a single byte, buffering it until the header block is complete.private voidwriteBody(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 voidParse the received header, adjust it as needed, and write it to the client.Methods inherited from class FilterOutputStream
flush, writeMethods inherited from class OutputStream
nullOutputStream
Field Details
_log
Logger for the HTTP response stream._headerBuffer
Pooled buffer holding the response header block while it is parsed, null once emitted._headerWritten
private volatile boolean _headerWrittenTrue once the header was written._buf1
private final byte[] _buf1Buffer for single byte writes._gzip
protected volatile boolean _gzipSet when the response carries Content-Encoding: x-i2p-gzip, so the body is gunzipped._dataExpected
protected volatile long _dataExpectedContent-Length of the body in bytes, -1 if unknown or chunked, 0 for a bodyless response._keepAliveIn
protected volatile boolean _keepAliveInTrue if the outproxy (I2P) side connection may be reused after this response._keepAliveOut
protected volatile boolean _keepAliveOutTrue if the browser side socket may be reused after this response._chunked
private boolean _chunkedTrue if the response body is chunked, set during header parsing._connectionSent
private boolean _connectionSentTrue once the Connection header has been sent, set during header parsing._contentType
Response Content-Type header value, lower-case and trimmed._contentEncoding
Response Content-Encoding header value, lower-case and trimmed._callback
Callback invoked when the stream finishes._bodyReceived
private volatile long _bodyReceivedEntity-body bytes delivered after the first header block (Range resume offset)._suppressHeaderOutput
private volatile boolean _suppressHeaderOutputSwallow the next header block (mid-body Range resume); do not emit to the browser._savedDataExpected
private long _savedDataExpectedContent-Length from the first response; preserved across a resume header parse._statusCode
private int _statusCodeHTTP status of the response currently being parsed._resumeSkipBytes
private long _resumeSkipBytesBody bytes to drop from the next response before forwarding (200 after Range)._transientResumeFailure
private volatile boolean _transientResumeFailureTrue when a transient status (408/502/503/504) aborted a Range resume — next attempt must re-request from byte 0 without Range.CACHE_SIZE
_cache
Cache supplying the write buffers.MAX_HEADER_SIZE
private static final int MAX_HEADER_SIZELargest header buffered before sending, to stop an OOM denial of service.- See Also:
NL
private static final byte NLWe ignore any potential \r, since we trim it on write anyway.- See Also:
CONNECTION_CLOSE
private static final byte[] CONNECTION_CLOSEThe Connection close header line.CRLF
private static final byte[] CRLFThe bare CRLF line.
Constructor Details
HTTPResponseOutputStream
Construct a response filter with no callback and no keepalive.- Parameters:
raw- the stream the filtered response is written to
HTTPResponseOutputStream
Optionally call callback when we're done.- Parameters:
raw- the underlying output streamcb- 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 toallowKeepAliveIn- 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
Write a single byte, buffering it until the header block is complete.- Overrides:
writein classFilterOutputStream- Throws:
IOException
write
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:
writein classFilterOutputStream- Parameters:
buf- source buffer holding header bytes, then body bytesoff- index in buf of the first byte to writelen- number of bytes to write from buf- Throws:
IOException- if the header exceeds the size limit or the underlying write fails
writeBody
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 peeroff- index of the first body byte within buflen- number of body bytes available in buf from off- Throws:
IOException- if the browser write fails
ensureCapacity
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
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
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
Split the header buffer into lines and process each one. Sets the response-scoped _chunked and _connectionSent flags.- Throws:
IOException- on malformed headers
handleResponseLine
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 bufferlen- the length of the response line, including the NLvalid- the total number of valid bytes in the buffer- Throws:
IOException- on a corrupt response line
handleHeaderLine
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 bufferstart- the first byte of the headerend- 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
Terminate the header block by writing the blank line that ends it.- Throws:
IOException- if the underlying stream cannot be written
close
Release the pooled header buffer and close the underlying stream.- Specified by:
closein interfaceAutoCloseable- Specified by:
closein interfaceCloseable- Overrides:
closein classFilterOutputStream- Throws:
IOException
beginProcessing
Start decoding the body, wrapping the output so x-i2p-gzip data is gunzipped.- Throws:
IOException- if the gunzip wrapper cannot be created