Troubleshooting

Timeouts Do Not Fire

applyTimeouts configures request timeout attributes. The Ktor client still needs the HttpTimeout plugin installed, and the selected Ktor engine must support the requested timeout type.

If a streaming endpoint is quiet for longer than the socket timeout, use:

import one.wabbit.web.common.Timeouts

val streaming = Timeouts().forStreaming()

HTTP Statuses Are Not Retried

retryingIdempotentHttpCall retries thrown failures. It sees HTTP status codes only when Ktor throws response exceptions, usually with expectSuccess = true or custom response validation.

When your client keeps expectSuccess = false, use retryingIdempotentHttpResponseCall or retryingIdempotentHttpResponseBodyCall so retries are based on returned HttpResponse.status.

Retry-After Is Too Large

Enable maxRetryAfterDelay in HttpRetryOptions to cap server-provided delays:

import one.wabbit.web.common.HttpRetryOptions
import one.wabbit.web.common.Schedule
import kotlin.time.Duration.Companion.seconds

val options =
    HttpRetryOptions.broadIdempotent(
        schedule = Schedule.retries(),
        maxRetryAfterDelay = 30.seconds,
    )

Body Samples Are Garbled

Body sampling reads raw bytes and decodes them as UTF-8 diagnostics. It ignores the response charset, and compressed bodies may look like binary noise unless the Ktor pipeline has already decoded them.

Sampling is destructive: it consumes and cancels the response body channel.

Header Validation Fails

Etiquette validates header names and values with Ktor's header validators. It also rejects User-Agent and Referer inside extraHeaders; use the dedicated constructor parameters for those headers.