hzz docs json-http.md
5.2 kB · 82 lines
Markdown
at main

JsonHttp cancellation and response bounds #

JsonHttp keeps existing GET, getString, POST and postFields call sites compatible. The optional constructor argument maxResponseBytes defaults to 4 MiB and accepts 1 through Int.MAX_VALUE bytes; zero/unlimited settings are not supported. Choose a smaller provider-specific limit when possible. No dependencies or versions change.

Ownership and cancellation #

Requests use OkHttp enqueue and a cancellable continuation. Cancellation calls that request's Call.cancel() immediately, including while queued, waiting for a ProviderHttp permit, connecting, awaiting headers, reading or decoding. It does not cancel the client's other requests. Existing permit interruption behavior and the 100 ms maximum permit sleep interval are unchanged; a raced permit is not refunded.

The callback owns the response throughout bounded reading and parsing and closes it before delivering a value. No Response crosses a coroutine dispatch boundary, so cancellation racing delivery cannot orphan it. Cancellation is checked between reads and before/after parsing. Synchronous serialization cannot be preempted inside a decoder: cancellation immediately cancels the call and resumes the caller with cancellation, but a running decoder returns on its own before its worker is released. Custom serializers must not block indefinitely or allocate unbounded data themselves.

On a rejected body, malformed JSON, read failure or cancellation the call is cancelled before closing the response, avoiding OkHttp's connection-reuse drain. Bytes already in socket/Okio buffers or in flight cannot be recalled. This is not a zero network-read-ahead guarantee. Successful responses may reuse their connections.

Finite decoded input #

A known decoded Content-Length above the bound is rejected before reading. Every successful body is also counted while reading, including unknown-length/chunked responses and gzip after decompression. Explicit Accept-Encoding gzip is handled as well as OkHttp's transparent gzip. Other remaining content encodings are rejected. The accumulation buffer accepts at most the limit plus one sentinel byte; exact-bound responses require EOF and succeed. Okio/gzip may retain fixed-size read-ahead buffers. String conversion preserves OkHttp charset/BOM handling. The bound is on decoded bytes, not characters, total JVM heap, or resulting object graph; strings/DTOs add bounded-input-dependent allocation. This is not a streaming JSON parser.

Non-2xx bodies are never read at all. Status errors retain their numeric code and 401/403 authorization classification even if the error body is oversized or gzip. Oversize, unsupported encoding and malformed JSON errors have fixed messages and no raw cause. This includes custom serializer Errors (such as AssertionError and StackOverflowError): they complete the caller with a sanitized failure, rather than escaping the worker callback and leaving the caller suspended. Transport/read/cleanup failures, including Errors, become a cause-free IOException with a fixed message. No URI, header or raw response text is included in these errors or logged. Caller-side URL/header construction and request serialization remain caller input validation responsibilities. Injected client interceptors must also avoid logging secrets and must not independently buffer unbounded responses.

Policy boundaries #

JsonHttp adds no retry loop. POST bodies are one-shot so OkHttp does not replay a mutation body on recovery or status follow-ups (including authentication and 503 Retry-After: 0). One-shot does not disable redirects: OkHttp may follow 301/302/303 by converting POST to GET with no body; 307/308 do not replay this POST. Callers needing to prohibit all redirects must supply a client with redirects disabled. JsonHttp never automatically retries the mutation itself. Application write reconciliation/retry ownership remains separate; cancellation does not prove that a server did not apply a write. ProviderHttp's required User-Agent is unchanged. The existing token buckets are not strict shared app/token Simkl quotas. This work makes no claim of real Simkl wire compatibility, production activation, or complete provider retry/backoff policy.

Verification #

MockWebServer loopback tests cover header/body/permit cancellation, sibling requests, response-delivery and decoding cancellation races, closure, exact limits, known and chunked oversize, transparent/explicit gzip, malformed gzip/JSON, unread rejected bodies, malicious declared lengths, dispatcher-queued cancellation, exact-bound EOF stalls (timeout/cancellation), decoder Errors without uncaught worker failures, authorization, User-Agent and POST redirect/authentication non-replay. Cancellation fixtures synchronize at body reads and require body closure and dispatcher release, accepting either legitimate terminal call event. Existing permit interrupt and account/client suites remain part of the full gate. No live providers/devices.

ANDROID_HOME=/home/dev/android-sdk ./gradlew test :app:assembleDebug --max-workers=2 --console=plain --offline
git diff --check