java.lang.Object
java.lang.Thread
net.i2p.util.I2PThread
net.i2p.util.I2PAppThread
net.i2p.i2ptunnel.I2PTunnelRunner
net.i2p.i2ptunnel.I2PTunnelHTTPClientRunner
- All Implemented Interfaces:
Runnable, LimitOutputStream.DoneCallback
HTTP client runner that filters response headers to enforce the "Connection: close" header, ensuring browsers treat the connection as non-persistent, even if the server doesn't comply.
This class is internal and not part of a stable public API.
Implements proper resource cleanup, supports optional keep-alive for both I2P and browser sockets, and uses background threading for non-blocking socket closure when appropriate.
Thread Safety: This class is not thread-safe. It is expected to be used by a single connection thread.
Nested Class Summary
Nested classes/interfaces inherited from class I2PTunnelRunner
I2PTunnelRunner.BodyProgress, I2PTunnelRunner.FailCallback, I2PTunnelRunner.RaceWin, I2PTunnelRunner.ReconnectCallback, I2PTunnelRunner.ResumeBudget, I2PTunnelRunner.SuccessCallbackModifier and TypeClassDescriptionprotected static final classSnapshot of body-delivery progress used byI2PTunnelRunner.shouldResumeIncompleteBody(long, long, boolean, boolean, boolean).static interfaceFor use in new constructor.(package private) static final classWinner of a dual-race empty retry: the socket that produced the first response byte, with a pushback stream that still holds that byte so the forwarder never loses the head of the response.static interfaceCallback for reconnecting via a second route when the first attempt completes with zero upstream bytes, or dies mid-body with an incomplete Content-Length response (Range resume).(package private) static final classProgress-based budget for body-resume cycles.static interfaceCallback interface for successful tunnel operation completion.Nested classes/interfaces inherited from class I2PThread
I2PThread.OOMEventListenerModifier and TypeClassDescriptionstatic interfaceListener for out-of-memory events.Nested classes/interfaces inherited from class Thread
Thread.Builder, Thread.State, Thread.UncaughtExceptionHandlerField Summary
Fields inherited from class I2PTunnelRunner
_keepAliveI2P, _keepAliveSocket, _log, BODY_STALL_READ_TIMEOUT_MS, EMPTY_CYCLE_BASE_DELAY_MS, EMPTY_CYCLE_MAX_DELAY_MS, EMPTY_RACE_TIMEOUT_MS, INITIAL_RESPONSE_TIMEOUT_MS, INITIAL_WATCHDOG_POLL_MS, initialResponseTimeoutMs, MAX_EMPTY_RECONNECT_CYCLES, MAX_PACKET_SIZE, MAX_RESUME_CYCLES, MAX_SCALED_RESUME_CYCLES, MAX_SCALED_STALL_CYCLES, NETWORK_BUFFER_SIZE, NO_DEADLINE, RACE_COST_TOKENS, RACE_REFILL_PER_SEC, RACE_STAGGER_MS, RACE_TOKENS_MAX, RESUME_DEADLINE_MS, RETRY_RAMP_MAX_BYTES, RETRY_RAMP_UNIT_BYTES, SCALED_CAP_MULTIPLIER, SINGLE_COST_TOKENSModifier and TypeFieldDescriptionprotected booleanKeep I2P socket alive after data transfer.protected booleanKeep local socket alive after data transfer.protected final LogThe runner's logger, from the global context's log manager.(package private) static final longNo-bytes read timeout (ms) once a response body has started streaming.(package private) static final longBase delay before empty-retry cycle N+1 (ms).(package private) static final longCap for empty-retry inter-cycle delay (ms).(package private) static final longOverall wait for a race winner's first response byte (ms).(package private) static final longDeadline for the very first response byte after a request is written upstream (ms).(package private) static final longWatchdog poll interval (ms); SimpleTimer2 rejects periods under 5s.(package private) static longEffective first-response deadline (ms), read per watchdog tick.(package private) static final intMax number of consecutive no-progress cycles (stalls) the outer empty-response / body-resume reconnect loop may invoke the callback for.(package private) static final intMax bytes streamed in a packet - smaller ones might be filled up to this size.(package private) static final intAbsolute safety cap on total body-resume cycles regardless of progress.(package private) static final intHard cap on the size-scaled total cycle budget (I2PTunnelRunner.SCALED_CAP_MULTIPLIERtimes baseline).(package private) static final intHard cap on the size-scaled stall budget (I2PTunnelRunner.SCALED_CAP_MULTIPLIERtimes baseline), bounding worst-case browser wait when the destination is genuinely stuck.(package private) static final intNetwork buffer size for buffered streams.(package private) static final longSentinel for "no wall-clock deadline", matchingI2PTunnelClientBase.NO_DEADLINE.(package private) static final intBudget cost of one dual-race open (two SYNs).(package private) static final intToken refill rate: tokens per second, keyed per remote dest.(package private) static final longStagger between the two race connects (ms) to avoid same-tick double SYN.(package private) static final intPer-dest race token bucket capacity (SYNs worth of budget).(package private) static final longWall-clock ceiling on one entity's resume/reconnect sequence (ms), independent of Content-Length.(package private) static final longLargest Content-Length that may enlarge the retry ramp (bytes).(package private) static final longEntity-size unit for retry-budget ramping: size-scaled limits gain one cycle (or connect attempt) per this many bytes of Content-Length.(package private) static final intMultiplier applied to a baseline budget to cap the size-scaled ramps.(package private) static final intBudget cost of one single-socket open on the empty path.Fields inherited from class Thread
MAX_PRIORITY, MIN_PRIORITY, NORM_PRIORITYConstructor Summary
ConstructorsConstructorDescriptionI2PTunnelHTTPClientRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List<I2PSocket> sockList, I2PTunnelRunner.FailCallback onFail, boolean allowKeepAliveI2P, boolean allowKeepAliveSocket, boolean isHead) Constructs a new HTTP client runner.Method Summary
All MethodsModifier and TypeMethodDescriptionprotected voidclose(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) Closes all streams and sockets, respecting keep-alive settings.protected I2PTunnelRunner.BodyProgressBody-delivery progress from the HTTP response stream, for Range resume.(package private) booleanShould we keep the I2P socket open when done? Checks the HTTPResponseOutputStream's keep-alive decision based on actual response headers.(package private) booleanShould we keep the local browser socket open when done? Checks the HTTPResponseOutputStream's keep-alive decision based on actual response headers.protected OutputStreamReturns a filtered HTTP response OutputStream that enforces the "Connection: close" header.protected booleanPeek at the latched transient-failure flag without clearing it — used by the forwarder finally-block to keep the browser stream open while the non-Range fallback is still pending.protected voidPut the response stream into mid-body resume mode (swallow next headers).protected booleanWhether this runner type arms the first-response deadline watchdog.protected booleanWhether the last body-resume attempt failed with a transient status (408/502/503/504) rather than a real stall.Methods inherited from class I2PTunnelRunner
checkInitialResponseDeadline, emptyCycleDelayMs, getRunnerExecutor, getSocketIn, getStartedOn, initialResponseExpired, isRetryableRequest, noteFirstResponseByte, noteRequestWritten, noteRequestWritten, onNoDataFailure, onNoDataFailure, pickRedriveStream, raceEmptyPair, refillRaceBudget, resumeDeadlineExpired, run, setExecutor, setReconnectCallback, setSuccessCallback, shouldFallbackFullBody, shouldFireNoDataFailure, shouldReconnectEmptyResponse, shouldResumeIncompleteBody, stallCycleLimit, streamDone, totalCycleLimit, tryConsumeRaceBudget, withoutRangeHeader, withRangeHeaderModifier and TypeMethodDescription(package private) booleancheckInitialResponseDeadline(long nowMs) Enforce the first-response deadline: close the I2P socket if no response byte arrived withinI2PTunnelRunner.initialResponseTimeoutMsof the last request-side write.(package private) static longemptyCycleDelayMs(int cycle) Delay before empty-retry cyclecycle(1-based, after the cycle counter has been incremented).(package private) ExecutorThe tunnel's runner executor (set byI2PTunnelRunner.setExecutor(Executor)), or null when the runner will spawn a dedicated thread instead.protected InputStreamGets the TCP socket input stream.longReturns the timestamp when this runner started.(package private) static booleaninitialResponseExpired(long lastRequestWriteMs, long firstByteMs, long nowMs, long timeoutMs) Whether the first-response deadline has elapsed for the current request attempt (pure predicate).(package private) static booleanisRetryableRequest(byte[] initialData) Whether the buffered initial request is safe to re-send on a fresh connection in the "empty response" retry.(package private) voidRecord the first response byte upstream: permanently retires the initial-response watchdog from here on (I2PTunnelRunner.BODY_STALL_READ_TIMEOUT_MScovers further stalling).(package private) voidRecord a request-side write upstream: start (or slide, or restart) the first-response deadline for the current attempt.(package private) voidnoteRequestWritten(long nowMs) I2PTunnelRunner.noteRequestWritten()with an explicit timestamp (test hook).protected voidInvoke the no-data failure callback when a transfer completes (or aborts) without delivering any bytes from the I2P peer.protected voidInvoke the no-data failure callback with an optional cause.(package private) static InputStreampickRedriveStream(InputStream supplied, I2PSocket sock) Choose the stream a re-driven receive forwarder reads from.(package private) I2PTunnelRunner.RaceWinraceEmptyPair(I2PSocket[] pair, byte[] request) Race two freshly connected sockets for an empty-response retry: write the buffered request to both, then the first socket to deliver a non-EOF byte wins.(package private) static long[]refillRaceBudget(long[] state, long now) Refill a per-dest race token bucket in place.(package private) static booleanresumeDeadlineExpired(long deadlineMs, long nowMs) Whether a resume sequence has run past its wall-clock deadline.voidrun()Serves one connection: writes the buffered request, starts the forwarders and keeps them going, reconnecting if the upstream returns nothing.voidsetExecutor(Executor exec) Set the executor for submitting forwarder tasks.voidSet the reconnect callback used for the empty-response retry.voidSets a callback to be invoked on successful data transfer.(package private) static booleanshouldFallbackFullBody(long bodyReceived, long dataExpected) Whether a non-Range full re-request is safe after a transient status (408/502/503/504) aborted a Range resume attempt.(package private) static booleanshouldFireNoDataFailure(long totalReceived, boolean handled) Whether a transfer with the given upstream byte count and handling state should trigger the no-data failure callback.(package private) static booleanshouldReconnectEmptyResponse(long totalReceived, boolean hasReconnectCallback, boolean retryableRequest) Whether an empty-upstream transfer should be retried on a fresh I2P connection.(package private) static booleanshouldResumeIncompleteBody(long bodyReceived, long dataExpected, boolean hasReconnectCallback, boolean retryableRequest, boolean canRangeResume) Whether an incomplete Content-Length body should be resumed via HTTP Range on a fresh I2P connection (new tunnel).(package private) static intstallCycleLimit(long contentLength) Size-scaled cap on consecutive no-progress resume cycles.voidThe DoneCallback for the I2P socket.(package private) static inttotalCycleLimit(long contentLength) Size-scaled cap on total body-resume cycles regardless of progress.(package private) static booleantryConsumeRaceBudget(long[] state, long now, int costTokens) Try to consumecostTokensfrom the race budget, refilling first.(package private) static byte[]withoutRangeHeader(byte[] request) Returnrequestwith everyRange:header line removed — the non-Range fallback form re-requested after a transient status (408/502/503/504) aborts a Range resume.(package private) static byte[]withRangeHeader(byte[] request, long start) Rewrite a buffered GET/HEAD request to resume atstartvia a Range header, replacing any Range the browser already sent.Methods inherited from class I2PAppThread
addOOMEventThreadListener, fireOOM, removeOOMEventThreadListenerModifier and TypeMethodDescriptionvoidRegister a new component that wants notification of OOM events.protected voidfireOOM(OutOfMemoryError oom) Notify listeners that an OutOfMemoryError occurred.voidUnregister a component that wants notification of OOM events.Methods inherited from class I2PThread
addOOMEventListener, removeOOMEventListener, startModifier and TypeMethodDescriptionstatic voidRegister a new component that wants notification of OOM events.static voidUnregister a component that wants notification of OOM events.voidstart()Overridden to provide useful info to users on OOM, and to prevent shutting down the whole JVM for what is most likely not a heap issue.Methods inherited from class Thread
activeCount, clone, currentThread, dumpStack, enumerate, getAllStackTraces, getContextClassLoader, getDefaultUncaughtExceptionHandler, getName, getPriority, getStackTrace, getState, getThreadGroup, getUncaughtExceptionHandler, holdsLock, interrupt, interrupted, isAlive, isDaemon, isInterrupted, isVirtual, join, join, join, join, ofPlatform, ofVirtual, onSpinWait, setContextClassLoader, setDaemon, setDefaultUncaughtExceptionHandler, setName, setPriority, setUncaughtExceptionHandler, sleep, sleep, sleep, startVirtualThread, threadId, toString, yield
Constructor Details
I2PTunnelHTTPClientRunner
public I2PTunnelHTTPClientRunner(Socket s, I2PSocket i2ps, Object slock, byte[] initialI2PData, List<I2PSocket> sockList, I2PTunnelRunner.FailCallback onFail, boolean allowKeepAliveI2P, boolean allowKeepAliveSocket, boolean isHead) Constructs a new HTTP client runner.- Parameters:
s- local socket (browser side)i2ps- connected I2P socketslock- synchronization lock for shared resourcesinitialI2PData- initial data to send over I2PsockList- list of active I2P socketsonFail- callback for failure eventsallowKeepAliveI2P- if true, optionally keep I2P socket alive after responseallowKeepAliveSocket- if true, optionally keep browser socket alive after responseisHead- true if responding to an HTTP HEAD request (no body expected)- Throws:
IllegalArgumentException- if keep-alive I2P is requested but browser socket keep-alive is disabled
Method Details
trackInitialResponseDeadline
protected boolean trackInitialResponseDeadline()Whether this runner type arms the first-response deadline watchdog. False in the base runner so idle long-lived sessions (HTTP CONNECT, SOCKS, IRC, server-side tunnels) are never torn down for silence; the HTTP request/response runner overrides it to true. For CONNECT the remote proxy's 200 response would retire the watchdog anyway, but the default keeps the deadline strictly scoped to request/response flows.True here: an HTTP request/response strictly precedes its response, so a peer that never sends the first byte must be torn down by the initial-response deadline instead of hanging the runner forever (read timeouts are disabled for HTTP tunnels and the body stall timeout is only armed after the first byte).
- Overrides:
trackInitialResponseDeadlinein classI2PTunnelRunner- Returns:
- true to schedule the initial-response watchdog
getSocketOut
Returns a filtered HTTP response OutputStream that enforces the "Connection: close" header. Should be called only once per instance.- Overrides:
getSocketOutin classI2PTunnelRunner- Returns:
- an output stream wrapping the socket's output stream that filters HTTP headers
- Throws:
IllegalStateException- if called more than onceIOException- if an I/O error occurs
getKeepAliveSocket
boolean getKeepAliveSocket()Should we keep the local browser socket open when done? Checks the HTTPResponseOutputStream's keep-alive decision based on actual response headers.- Overrides:
getKeepAliveSocketin classI2PTunnelRunner- Returns:
- true if the local socket should remain open for reuse
getKeepAliveI2P
boolean getKeepAliveI2P()Should we keep the I2P socket open when done? Checks the HTTPResponseOutputStream's keep-alive decision based on actual response headers.- Overrides:
getKeepAliveI2Pin classI2PTunnelRunner- Returns:
- true if the I2P socket should remain open for reuse
getBodyProgress
Body-delivery progress from the HTTP response stream, for Range resume.- Overrides:
getBodyProgressin classI2PTunnelRunner- Returns:
- progress snapshot, or null if the response stream is not up yet
prepareBodyResume
protected void prepareBodyResume()Put the response stream into mid-body resume mode (swallow next headers).- Overrides:
prepareBodyResumein classI2PTunnelRunner
wasTransientResumeFailure
protected boolean wasTransientResumeFailure()Whether the last body-resume attempt failed with a transient status (408/502/503/504) rather than a real stall. Observed once, then cleared so the next attempt starts fresh.- Overrides:
wasTransientResumeFailurein classI2PTunnelRunner- Returns:
- true if the last resume aborted with a transient status
hasTransientResumeFailure
protected boolean hasTransientResumeFailure()Peek at the latched transient-failure flag without clearing it — used by the forwarder finally-block to keep the browser stream open while the non-Range fallback is still pending.- Overrides:
hasTransientResumeFailurein classI2PTunnelRunner- Returns:
- true if a transient status aborted the last resume attempt
close
protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException Closes all streams and sockets, respecting keep-alive settings. Optionally closes I2P socket in a background thread if keep-alive is enabled on the browser socket.- Overrides:
closein classI2PTunnelRunner- Parameters:
out- may be nullin- may be nulli2pout- may be nulli2pin- may be nulls- the local socket to close, non-nulli2ps- the I2P socket to close, non-nullt1- may be nullt2- may be null, ignored, we only join t1- Throws:
InterruptedException- if the joining thread is interrupted while waiting on t1