OkHttp HTTP Client

repository·main·Indexed 13 days ago

https://github.com/lysine-dev/okhttp

An efficient, high-performance HTTP client for Java and Android supporting HTTP/2, TLS 1.3, and WebSockets. Includes MockWebServer for testing with JUnit 4 and 5 integrations, Brotli compression support, Kotlin coroutines for asynchronous calls, and DNS over HTTPS (DoH) implementation. Version 5.4.0.

Tokens
41.3K
Snippets
131
Records
201
Agent score
99%

What's inside OkHttp

  1. Compatible libraries and tools for OkHttp

    main

    OkHttp integrates with a wide ecosystem of libraries for debugging, image loading, networking, and testing. Key categories include:

    Debugging & Inspection

    • Chucker: In-app HTTP inspector for Android.
    • Flipper: Desktop debugging platform for mobile.
    • Ok2Curl: Converts OkHttp requests into curl logs.
    • OkHttp Profiler: IntelliJ plugin for monitoring calls.
    • Stetho: Debug bridge for Android applications.
    • OkLog: Response logging interceptor that logs URL links with URL-encoded responses.

    Image Loading (Android)

    • Coil: Image loading backed by Kotlin Coroutines.
    • Fresco: Facebook's library for managing images and memory.
    • Glide: Focused on smooth scrolling and caching.
    • Picasso: Powerful image downloading and caching.

    Networking & Protocol Extensions

    • Cronet Transport for OkHttp: HTTP/3 ready transport layer for Android (based on Chromium).
    • Moshi: Modern JSON library.
    • Okio: Modern I/O API for Java.
    • Retrofit: Type-safe HTTP client.
    • Wire: Lightweight protocol buffers.
    • PersistentCookieJar: A persistent implementation of CookieJar.
    • okhttp-aws-signer: AWS V4 signing algorithm.
    • okhttp-signpost: OAuth signing.
    • ScribeJava: Simple OAuth library for Java.
    • okhttp-digest: Digest authenticator.

    Resilience & Testing

    • Failsafe: Fault tolerance and resilience patterns.
    • OkReplay: Record and replay network interactions in tests.
    • okhttp-client-mock: Simple client mock using a programmable request interceptor.
    • OkHttp Idling Resource: Espresso IdlingResource for Android testing.

    Other Integrations

    • okhttp-spring-boot: Spring Boot starters.
    • okhttp-stats: Provides network statistics like average speed.
    • okhttp-system-keystore: Uses OS-level trusted certificates (Keychain on macOS, Certificate Store on Windows).
  2. Use Call.Factory for easier testing

    main
    In OkHttp 3.x, OkHttpClient implements the Call.Factory interface. This abstraction makes it easier to write testable code. Instead of depending directly on a concrete OkHttpClient, your code should depend on the Call.Factory interface. This allows you to easily swap the real OkHttpClient with a mock or fake implementation during unit testing.
  3. Use a Dispatcher for complex request routing

    main

    While server.enqueue() works for a simple sequence of responses, a Dispatcher allows you to implement custom logic to decide which MockResponse to return based on the incoming RecordedRequest. This is useful for routing requests to different responses based on the URL path or other request properties.

    To use a Dispatcher, extend the Dispatcher class and override the dispatch(RecordedRequest request) method, then register it with server.setDispatcher(dispatcher) (Java) or server.dispatcher = dispatcher (Kotlin).

    final Dispatcher dispatcher = new Dispatcher() {
        @Override
        public MockResponse dispatch(RecordedRequest request) {
            switch (request.getUrl().encodedPath()) {
              case "/v1/login/auth/":
                  return new MockResponse.Builder().code(200).build();
              case "/v1/check/version/":
                  return new MockResponse.Builder().body("version=9").build();
              default:
                  return new MockResponse.Builder().code(404).build();
            }
        }
    };
    server.setDispatcher(dispatcher);
  4. The OkHttp connection lifecycle

    main

    When requesting a URL, OkHttp follows these steps:

    1. Create Address: Uses the URL and OkHttpClient configuration to define the target webserver and static settings.
    2. Pool Lookup: Attempts to find an existing connection for that Address in the ConnectionPool.
    3. Route Selection: If no pooled connection exists, it selects a Route (typically involving a DNS request to find IP addresses, proxy selection, and TLS version selection).
    4. Connection Establishment: Builds a direct socket, a TLS tunnel (for HTTPS over HTTP proxy), or a direct TLS connection. This step may retry for tunnel challenges or TLS handshake failures.
    5. Request/Response: Sends the HTTP request and reads the response.

    Error Recovery: If a connection fails, OkHttp selects a different Route and retries. This allows recovery if certain IP addresses are unreachable or if a pooled connection is stale.

  5. Customize Proxy Fallback Behavior

    main
    In OkHttp 3.5 and later, the library no longer attempts a direct connection if the system's HTTP proxy fails. If your application requires custom fallback behavior (e.g., attempting a direct connection after a proxy failure), you must implement your own java.net.ProxySelector.
  6. Monitor upload progress with ProgressRequestBody

    main

    To report upload progress (e.g., for large file uploads to services like Imgur), you can wrap a standard RequestBody with a custom implementation that uses a ProgressListener. This involves creating a ForwardingSink to intercept the write calls and track the number of bytes written against the total contentLength().

    // Kotlin implementation pattern
    fun interface ProgressListener {
      fun update(bytesWritten: Long, contentLength: Long, done: Boolean)
    }
    
    private class ProgressRequestBody(
      private val delegate: RequestBody, 
      private val progressListener: ProgressListener
    ) : RequestBody() {
      // ... implementation using ForwardingSink to call progressListener.update() ...
    }
  7. Understand OkHttp TLS configuration modes

    main

    OkHttp provides different TLS configuration profiles to balance connectivity and security. Understanding these modes helps you choose the right security posture for your application:

    • RESTRICTED_TLS: An extra-strict configuration. It is appropriate when both your host platform (JVM/Conscrypt/Android) and the target webserver are current. It limits supported versions and cipher suites to a highly secure subset.
    • MODERN_TLS: A secure configuration that prioritizes modern protocols (like TLSv1.3 and TLSv1.2) while removing older, insecure versions like TLSv1.1 and TLSv1.
    • COMPATIBLE_TLS: A broad configuration designed for maximum connectivity. It supports all TLS versions (including TLSv1 and TLSv1.1) to ensure compatibility with older servers.
  8. Implement duplex calls over HTTP/2

    main

    OkHttp 3.14.0 introduced support for duplex calls, where request and response bodies are transmitted simultaneously. This is useful for interactive conversations (e.g., gRPC) within a single HTTP call.

    To implement a duplex call, override RequestBody.isDuplex() to return true.

    Important considerations for duplex calls:

    • HTTP/2 Requirement: Duplex calls require HTTP/2. If the connection falls back to HTTP/1, the call will fail.
    • Thread Safety in writeTo(): The RequestBody.writeTo() method may retain a reference to the provided sink and hand it off to another thread to write to it after the method returns.
    • Interleaved Events: EventListener may see requests and responses interleaved (e.g., responseHeadersStart() followed by requestBodyEnd() on the same call). These events may be triggered by different threads.
    • Interceptor Caution: Interceptors that rewrite or replace the request body may interfere with duplex calls. Interceptors should check RequestBody.isDuplex() and avoid accessing the request body when it is true.
    // Example pattern for a duplex request body
    class MyDuplexBody : RequestBody() {
        override fun contentType(): MediaType? = "application/octet-stream".toMediaType()
        
        override fun isDuplex(): Boolean = true
    
        override fun writeTo(sink: BufferedSink) {
            // Implementation may hand off the sink to another thread
        }
    }