Repository: square/okhttp
Branch: master
Commit: 2ae6e02c2efa
Files: 783
Total size: 5.7 MB
Directory structure:
gitextract_j2sj9obc/
├── .devcontainer/
│ └── devcontainer.json
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── feature_request.md
│ │ └── question.md
│ ├── renovate.json
│ └── workflows/
│ ├── build.yml
│ ├── containers.yml
│ ├── docs.yml
│ └── publish.yml
├── .gitignore
├── .gitmodules
├── .junit.run/
│ └── Not Slow.run.xml
├── .vscode/
│ └── settings.json
├── BUG-BOUNTY.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── android-test/
│ ├── build.gradle.kts
│ └── src/
│ ├── androidDeviceTest/
│ │ ├── README.md
│ │ └── java/
│ │ └── okhttp/
│ │ └── android/
│ │ └── test/
│ │ ├── OkHttpTest.kt
│ │ ├── SingleAndroidTest.kt
│ │ ├── StrictModeTest.kt
│ │ ├── alpn/
│ │ │ └── AlpnOverrideTest.kt
│ │ ├── letsencrypt/
│ │ │ └── LetsEncryptClientTest.kt
│ │ └── sni/
│ │ └── SniOverrideTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ └── res/
│ │ ├── values/
│ │ │ └── strings.xml
│ │ └── xml/
│ │ └── network_security_config.xml
│ └── test/
│ └── kotlin/
│ └── okhttp/
│ └── android/
│ └── test/
│ ├── AndroidLoggingTest.kt
│ ├── AndroidSocketAdapterTest.kt
│ ├── BaseOkHttpClientUnitTest.kt
│ ├── DisabledInitialiserTest.kt
│ ├── NonRobolectricOkHttpClientTest.kt
│ ├── RobolectricOkHttpClientTest.kt
│ └── ShadowDnsResolver.kt
├── android-test-app/
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ ├── src/
│ │ ├── androidTest/
│ │ │ └── kotlin/
│ │ │ └── okhttp/
│ │ │ └── android/
│ │ │ └── testapp/
│ │ │ ├── IdnaTest.kt
│ │ │ └── PublicSuffixDatabaseTest.kt
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin/
│ │ │ └── okhttp/
│ │ │ └── android/
│ │ │ └── testapp/
│ │ │ ├── MainActivity.kt
│ │ │ ├── MainActivity2.kt
│ │ │ └── TestApplication.kt
│ │ └── res/
│ │ ├── values/
│ │ │ └── strings.xml
│ │ └── xml/
│ │ └── network_security_config.xml
│ └── test-proguard-rules.pro
├── build-logic/
│ ├── build.gradle.kts
│ ├── settings.gradle.kts
│ └── src/
│ └── main/
│ └── kotlin/
│ ├── AlpnVersions.kt
│ ├── BndBuildAction.kt
│ ├── JavaModules.kt
│ ├── Osgi.kt
│ ├── okhttp.base-conventions.gradle.kts
│ ├── okhttp.dokka-multimodule-conventions.gradle.kts
│ ├── okhttp.jvm-conventions.gradle.kts
│ ├── okhttp.publish-conventions.gradle.kts
│ ├── okhttp.quality-conventions.gradle.kts
│ ├── okhttp.testing-conventions.gradle.kts
│ └── okhttp3/
│ └── buildsupport/
│ └── OkHttpBuildUtils.kt
├── build.gradle.kts
├── container-tests/
│ ├── README.md
│ ├── build.gradle.kts
│ └── src/
│ └── test/
│ └── java/
│ └── okhttp3/
│ └── containers/
│ ├── BasicLoomTest.kt
│ ├── BasicMockServerTest.kt
│ ├── BasicProxyTest.kt
│ └── SocksProxyTest.kt
├── deploy_website.sh
├── docs/
│ ├── assets/
│ │ └── css/
│ │ └── app.css
│ ├── changelogs/
│ │ ├── changelog_1x.md
│ │ ├── changelog_2x.md
│ │ ├── changelog_3x.md
│ │ ├── changelog_4x.md
│ │ └── upgrading_to_okhttp_4.md
│ ├── contribute/
│ │ ├── code_of_conduct.md
│ │ ├── concurrency.md
│ │ ├── contributing.md
│ │ └── debug_logging.md
│ ├── features/
│ │ ├── caching.md
│ │ ├── calls.md
│ │ ├── connections.md
│ │ ├── events.md
│ │ ├── https.md
│ │ ├── interceptors.md
│ │ └── r8_proguard.md
│ ├── recipes.md
│ ├── releasing.md
│ ├── security/
│ │ ├── security.md
│ │ ├── security_providers.md
│ │ └── tls_configuration_history.md
│ └── works_with_okhttp.md
├── fuzzing/
│ ├── fuzzingserver-config.json
│ ├── fuzzingserver-expected.txt
│ ├── fuzzingserver-test.sh
│ └── fuzzingserver-update-expected.sh
├── gradle/
│ ├── gradle-daemon-jvm.properties
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── maven-tests/
│ ├── .gitignore
│ ├── .mvn/
│ │ ├── jvm.config
│ │ ├── maven.config
│ │ └── wrapper/
│ │ └── maven-wrapper.properties
│ ├── mvnw
│ ├── mvnw.cmd
│ ├── pom.xml
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── com/
│ │ └── squareup/
│ │ └── okhttp3/
│ │ └── maventest/
│ │ └── SampleHttpClient.java
│ └── test/
│ └── java/
│ └── com/
│ └── squareup/
│ └── okhttp3/
│ └── maventest/
│ └── AppTest.java
├── mkdocs.yml
├── mockwebserver/
│ ├── Module.md
│ ├── README.md
│ ├── api/
│ │ └── mockwebserver3.api
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java9/
│ │ │ └── module-info.java
│ │ └── kotlin/
│ │ └── mockwebserver3/
│ │ ├── Dispatcher.kt
│ │ ├── MockResponse.kt
│ │ ├── MockResponseBody.kt
│ │ ├── MockWebServer.kt
│ │ ├── PushPromise.kt
│ │ ├── QueueDispatcher.kt
│ │ ├── RecordedRequest.kt
│ │ ├── SocketEffect.kt
│ │ ├── SocketHandler.kt
│ │ └── internal/
│ │ ├── BufferMockResponseBody.kt
│ │ ├── MockWebServerSocket.kt
│ │ ├── RecordedRequestFactory.kt
│ │ ├── ThrottledSink.kt
│ │ └── TriggerSink.kt
│ └── test/
│ └── java/
│ └── mockwebserver3/
│ ├── CustomDispatcherTest.kt
│ ├── MockResponseSniTest.kt
│ ├── MockWebServerTest.kt
│ ├── RecordedRequestTest.kt
│ └── internal/
│ └── http2/
│ └── Http2Server.kt
├── mockwebserver-deprecated/
│ ├── api/
│ │ └── mockwebserver.api
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java9/
│ │ │ └── module-info.java
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ └── mockwebserver/
│ │ ├── DeprecationBridge.kt
│ │ ├── Dispatcher.kt
│ │ ├── MockResponse.kt
│ │ ├── MockWebServer.kt
│ │ ├── PushPromise.kt
│ │ ├── QueueDispatcher.kt
│ │ ├── RecordedRequest.kt
│ │ └── SocketPolicy.kt
│ └── test/
│ └── java/
│ └── okhttp3/
│ └── mockwebserver/
│ ├── KotlinSourceModernTest.kt
│ └── MockWebServerTest.kt
├── mockwebserver-junit4/
│ ├── README.md
│ ├── api/
│ │ └── mockwebserver3-junit4.api
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java9/
│ │ │ └── module-info.java
│ │ └── kotlin/
│ │ └── mockwebserver3/
│ │ └── junit4/
│ │ └── MockWebServerRule.kt
│ └── test/
│ └── java/
│ └── mockwebserver3/
│ └── junit4/
│ └── MockWebServerRuleTest.kt
├── mockwebserver-junit5/
│ ├── README.md
│ ├── api/
│ │ └── mockwebserver3-junit5.api
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java9/
│ │ │ └── module-info.java
│ │ └── kotlin/
│ │ └── mockwebserver3/
│ │ └── junit5/
│ │ ├── StartStop.kt
│ │ └── internal/
│ │ └── StartStopExtension.kt
│ └── test/
│ └── java/
│ └── mockwebserver3/
│ └── junit5/
│ └── StartStopTest.kt
├── module-tests/
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ └── java/
│ │ ├── module-info.java
│ │ └── okhttp3/
│ │ └── modules/
│ │ ├── Main.java
│ │ └── OkHttpCaller.java
│ └── test/
│ └── java/
│ ├── module-info.java
│ └── okhttp3/
│ └── modules/
│ └── test/
│ └── JavaModuleTest.java
├── native-image-tests/
│ ├── README.md
│ ├── build.gradle.kts
│ └── src/
│ └── test/
│ ├── kotlin/
│ │ └── okhttp3/
│ │ └── nativeimage/
│ │ ├── PublicSuffixDatabaseTest.kt
│ │ ├── SampleTest.kt
│ │ └── WithArgumentSourceTest.kt
│ └── resources/
│ └── META-INF/
│ └── native-image/
│ └── okhttp/
│ └── nit/
│ └── resource-config.json
├── okcurl/
│ ├── README.md
│ ├── build.gradle.kts
│ ├── okcurl
│ └── src/
│ ├── main/
│ │ ├── kotlin/
│ │ │ └── okhttp3/
│ │ │ └── curl/
│ │ │ ├── Main.kt
│ │ │ ├── MainCommandLine.kt
│ │ │ ├── internal/
│ │ │ │ └── -MainCommon.kt
│ │ │ └── logging/
│ │ │ ├── LoggingUtil.kt
│ │ │ ├── MessageFormatter.kt
│ │ │ └── OneLineLogFormat.kt
│ │ ├── resources/
│ │ │ └── META-INF/
│ │ │ └── native-image/
│ │ │ └── okhttp3/
│ │ │ └── okcurl/
│ │ │ ├── reflect-config.json
│ │ │ └── resource-config.json
│ │ └── resources-templates/
│ │ └── okcurl-version.properties
│ └── test/
│ └── kotlin/
│ └── okhttp3/
│ └── curl/
│ └── MainTest.kt
├── okhttp/
│ ├── Module.md
│ ├── api/
│ │ ├── android/
│ │ │ └── okhttp.api
│ │ └── jvm/
│ │ └── okhttp.api
│ ├── build.gradle.kts
│ ├── okhttp3.pro
│ └── src/
│ ├── androidHostTest/
│ │ ├── kotlin/
│ │ │ └── okhttp3/
│ │ │ └── internal/
│ │ │ └── publicsuffix/
│ │ │ └── PublicSuffixTesting.android.kt
│ │ └── resources/
│ │ └── okhttp3/
│ │ └── robolectric.properties
│ ├── androidMain/
│ │ ├── AndroidManifest.xml
│ │ ├── assets/
│ │ │ └── PublicSuffixDatabase.list
│ │ ├── baseline-prof.txt
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ ├── OkHttp.android.kt
│ │ └── internal/
│ │ ├── platform/
│ │ │ ├── Android10Platform.kt
│ │ │ ├── AndroidPlatform.kt
│ │ │ ├── ContextAwarePlatform.kt
│ │ │ ├── PlatformInitializer.kt
│ │ │ ├── PlatformRegistry.kt
│ │ │ └── android/
│ │ │ ├── Android10SocketAdapter.kt
│ │ │ ├── AndroidCertificateChainCleaner.kt
│ │ │ ├── AndroidLog.kt
│ │ │ ├── AndroidSocketAdapter.kt
│ │ │ ├── BouncyCastleSocketAdapter.kt
│ │ │ ├── ConscryptSocketAdapter.kt
│ │ │ ├── DeferredSocketAdapter.kt
│ │ │ ├── SocketAdapter.kt
│ │ │ └── StandardAndroidSocketAdapter.kt
│ │ └── publicsuffix/
│ │ ├── AssetPublicSuffixList.kt
│ │ └── PublicSuffixList.android.kt
│ ├── commonJvmAndroid/
│ │ ├── kotlin/
│ │ │ └── okhttp3/
│ │ │ ├── Address.kt
│ │ │ ├── Authenticator.kt
│ │ │ ├── Cache.kt
│ │ │ ├── CacheControl.kt
│ │ │ ├── Call.kt
│ │ │ ├── Callback.kt
│ │ │ ├── CertificatePinner.kt
│ │ │ ├── Challenge.kt
│ │ │ ├── CipherSuite.kt
│ │ │ ├── CompressionInterceptor.kt
│ │ │ ├── Connection.kt
│ │ │ ├── ConnectionPool.kt
│ │ │ ├── ConnectionSpec.kt
│ │ │ ├── Cookie.kt
│ │ │ ├── CookieJar.kt
│ │ │ ├── Credentials.kt
│ │ │ ├── Dispatcher.kt
│ │ │ ├── Dns.kt
│ │ │ ├── EventListener.kt
│ │ │ ├── FormBody.kt
│ │ │ ├── Gzip.kt
│ │ │ ├── Handshake.kt
│ │ │ ├── Headers.kt
│ │ │ ├── HttpUrl.kt
│ │ │ ├── Interceptor.kt
│ │ │ ├── MediaType.kt
│ │ │ ├── MultipartBody.kt
│ │ │ ├── MultipartReader.kt
│ │ │ ├── OkHttp.kt
│ │ │ ├── OkHttpClient.kt
│ │ │ ├── Protocol.kt
│ │ │ ├── Request.kt
│ │ │ ├── RequestBody.kt
│ │ │ ├── Response.kt
│ │ │ ├── ResponseBody.kt
│ │ │ ├── Route.kt
│ │ │ ├── TlsVersion.kt
│ │ │ ├── TrailersSource.kt
│ │ │ ├── WebSocket.kt
│ │ │ ├── WebSocketListener.kt
│ │ │ └── internal/
│ │ │ ├── -CacheControlCommon.kt
│ │ │ ├── -HeadersCommon.kt
│ │ │ ├── -HostnamesCommon.kt
│ │ │ ├── -NormalizeJvm.kt
│ │ │ ├── -UtilCommon.kt
│ │ │ ├── -UtilJvm.kt
│ │ │ ├── IsProbablyUtf8.kt
│ │ │ ├── NativeImageTestsAccessors.kt
│ │ │ ├── SuppressSignatureCheck.kt
│ │ │ ├── Tags.kt
│ │ │ ├── UnreadableResponseBody.kt
│ │ │ ├── authenticator/
│ │ │ │ └── JavaNetAuthenticator.kt
│ │ │ ├── cache/
│ │ │ │ ├── CacheInterceptor.kt
│ │ │ │ ├── CacheRequest.kt
│ │ │ │ ├── CacheStrategy.kt
│ │ │ │ ├── DiskLruCache.kt
│ │ │ │ └── FaultHidingSink.kt
│ │ │ ├── concurrent/
│ │ │ │ ├── Lockable.kt
│ │ │ │ ├── Task.kt
│ │ │ │ ├── TaskLogger.kt
│ │ │ │ ├── TaskQueue.kt
│ │ │ │ └── TaskRunner.kt
│ │ │ ├── connection/
│ │ │ │ ├── AddressPolicy.kt
│ │ │ │ ├── BufferedSocket.kt
│ │ │ │ ├── ConnectInterceptor.kt
│ │ │ │ ├── ConnectPlan.kt
│ │ │ │ ├── ConnectionListener.kt
│ │ │ │ ├── Exchange.kt
│ │ │ │ ├── ExchangeFinder.kt
│ │ │ │ ├── FailedPlan.kt
│ │ │ │ ├── FastFallbackExchangeFinder.kt
│ │ │ │ ├── ForceConnectRoutePlanner.kt
│ │ │ │ ├── InetAddressOrder.kt
│ │ │ │ ├── RealCall.kt
│ │ │ │ ├── RealConnection.kt
│ │ │ │ ├── RealConnectionPool.kt
│ │ │ │ ├── RealRoutePlanner.kt
│ │ │ │ ├── RetryTlsHandshake.kt
│ │ │ │ ├── ReusePlan.kt
│ │ │ │ ├── RouteDatabase.kt
│ │ │ │ ├── RoutePlanner.kt
│ │ │ │ ├── RouteSelector.kt
│ │ │ │ └── SequentialExchangeFinder.kt
│ │ │ ├── http/
│ │ │ │ ├── BridgeInterceptor.kt
│ │ │ │ ├── CallServerInterceptor.kt
│ │ │ │ ├── DateFormatting.kt
│ │ │ │ ├── ExchangeCodec.kt
│ │ │ │ ├── GzipRequestBody.kt
│ │ │ │ ├── HttpHeaders.kt
│ │ │ │ ├── HttpMethod.kt
│ │ │ │ ├── HttpStatusCodes.kt
│ │ │ │ ├── RealInterceptorChain.kt
│ │ │ │ ├── RealResponseBody.kt
│ │ │ │ ├── RequestLine.kt
│ │ │ │ ├── RetryAndFollowUpInterceptor.kt
│ │ │ │ └── StatusLine.kt
│ │ │ ├── http1/
│ │ │ │ ├── HeadersReader.kt
│ │ │ │ └── Http1ExchangeCodec.kt
│ │ │ ├── http2/
│ │ │ │ ├── ConnectionShutdownException.kt
│ │ │ │ ├── ErrorCode.kt
│ │ │ │ ├── FlowControlListener.kt
│ │ │ │ ├── Header.kt
│ │ │ │ ├── Hpack.kt
│ │ │ │ ├── Http2.kt
│ │ │ │ ├── Http2Connection.kt
│ │ │ │ ├── Http2ExchangeCodec.kt
│ │ │ │ ├── Http2Reader.kt
│ │ │ │ ├── Http2Stream.kt
│ │ │ │ ├── Http2Writer.kt
│ │ │ │ ├── Huffman.kt
│ │ │ │ ├── PushObserver.kt
│ │ │ │ ├── Settings.kt
│ │ │ │ ├── StreamResetException.kt
│ │ │ │ └── flowcontrol/
│ │ │ │ └── WindowCounter.kt
│ │ │ ├── idn/
│ │ │ │ ├── IdnaMappingTable.kt
│ │ │ │ └── Punycode.kt
│ │ │ ├── internal.kt
│ │ │ ├── platform/
│ │ │ │ ├── Jdk9Platform.kt
│ │ │ │ ├── Platform.kt
│ │ │ │ └── PlatformRegistry.kt
│ │ │ ├── proxy/
│ │ │ │ └── NullProxySelector.kt
│ │ │ ├── publicsuffix/
│ │ │ │ ├── BasePublicSuffixList.kt
│ │ │ │ ├── PublicSuffixDatabase.kt
│ │ │ │ ├── PublicSuffixList.kt
│ │ │ │ └── ResourcePublicSuffixList.kt
│ │ │ ├── tls/
│ │ │ │ ├── BasicCertificateChainCleaner.kt
│ │ │ │ ├── BasicTrustRootIndex.kt
│ │ │ │ ├── CertificateChainCleaner.kt
│ │ │ │ ├── OkHostnameVerifier.kt
│ │ │ │ └── TrustRootIndex.kt
│ │ │ ├── url/
│ │ │ │ └── -Url.kt
│ │ │ └── ws/
│ │ │ ├── MessageDeflater.kt
│ │ │ ├── MessageInflater.kt
│ │ │ ├── RealWebSocket.kt
│ │ │ ├── WebSocketExtensions.kt
│ │ │ ├── WebSocketProtocol.kt
│ │ │ ├── WebSocketReader.kt
│ │ │ └── WebSocketWriter.kt
│ │ └── kotlinTemplates/
│ │ └── okhttp3/
│ │ └── internal/
│ │ └── -InternalVersion.kt
│ ├── commonTest/
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ ├── CompressionInterceptorTest.kt
│ │ ├── OkHttpTest.kt
│ │ └── internal/
│ │ ├── IsProbablyUtf8Test.kt
│ │ └── publicsuffix/
│ │ ├── ConfiguredPublicSuffixDatabaseTest.kt
│ │ ├── ConfiguredPublicSuffixList.kt
│ │ ├── PublicSuffixDatabaseTest.kt
│ │ └── PublicSuffixTesting.kt
│ ├── jvmMain/
│ │ ├── java9/
│ │ │ └── module-info.java
│ │ ├── kotlin/
│ │ │ └── okhttp3/
│ │ │ ├── OkHttp.jvm.kt
│ │ │ └── internal/
│ │ │ ├── graal/
│ │ │ │ ├── GraalSvm.kt
│ │ │ │ └── OkHttpFeature.kt
│ │ │ ├── platform/
│ │ │ │ ├── BouncyCastlePlatform.kt
│ │ │ │ ├── ConscryptPlatform.kt
│ │ │ │ ├── Jdk8WithJettyBootPlatform.kt
│ │ │ │ ├── OpenJSSEPlatform.kt
│ │ │ │ └── PlatformRegistry.kt
│ │ │ └── publicsuffix/
│ │ │ └── PublicSuffixList.jvm.kt
│ │ └── resources/
│ │ ├── META-INF/
│ │ │ └── native-image/
│ │ │ └── okhttp/
│ │ │ └── okhttp/
│ │ │ ├── native-image.properties
│ │ │ ├── reflect-config.json
│ │ │ └── resource-config.json
│ │ └── okhttp3/
│ │ └── internal/
│ │ └── publicsuffix/
│ │ └── PublicSuffixDatabase.list
│ ├── jvmTest/
│ │ ├── java/
│ │ │ └── okhttp3/
│ │ │ └── CallJavaTest.java
│ │ ├── kotlin/
│ │ │ └── okhttp3/
│ │ │ ├── AddressTest.kt
│ │ │ ├── AutobahnTester.kt
│ │ │ ├── BouncyCastleTest.kt
│ │ │ ├── CacheControlJvmTest.kt
│ │ │ ├── CacheControlTest.kt
│ │ │ ├── CacheCorruptionTest.kt
│ │ │ ├── CacheTest.kt
│ │ │ ├── CallHandshakeTest.kt
│ │ │ ├── CallKotlinTest.kt
│ │ │ ├── CallLimitsTest.kt
│ │ │ ├── CallTagsTest.kt
│ │ │ ├── CallTest.kt
│ │ │ ├── CertificateChainCleanerTest.kt
│ │ │ ├── CertificatePinnerKotlinTest.kt
│ │ │ ├── CertificatePinnerTest.kt
│ │ │ ├── ChannelSocketFactory.kt
│ │ │ ├── CipherSuiteTest.kt
│ │ │ ├── CommonRequestBodyTest.kt
│ │ │ ├── ConnectionCoalescingTest.kt
│ │ │ ├── ConnectionListenerTest.kt
│ │ │ ├── ConnectionReuseTest.kt
│ │ │ ├── ConnectionSpecTest.kt
│ │ │ ├── ConscryptTest.kt
│ │ │ ├── CookieTest.kt
│ │ │ ├── CookiesTest.kt
│ │ │ ├── CorrettoTest.kt
│ │ │ ├── DispatcherCleanupTest.kt
│ │ │ ├── DispatcherTest.kt
│ │ │ ├── DuplexTest.kt
│ │ │ ├── EventListenerTest.kt
│ │ │ ├── FakeRoutePlanner.kt
│ │ │ ├── FallbackTestClientSocketFactory.kt
│ │ │ ├── FastFallbackTest.kt
│ │ │ ├── FormBodyTest.kt
│ │ │ ├── HandshakeTest.kt
│ │ │ ├── HeadersChallengesTest.kt
│ │ │ ├── HeadersJvmTest.kt
│ │ │ ├── HeadersKotlinTest.kt
│ │ │ ├── HeadersRequestTest.kt
│ │ │ ├── HeadersTest.kt
│ │ │ ├── HttpUrlJvmTest.kt
│ │ │ ├── HttpUrlTest.kt
│ │ │ ├── InsecureForHostTest.kt
│ │ │ ├── InterceptorOverridesTest.kt
│ │ │ ├── InterceptorTest.kt
│ │ │ ├── JSSETest.kt
│ │ │ ├── KotlinDeprecationErrorTest.kt
│ │ │ ├── KotlinSourceModernTest.kt
│ │ │ ├── LoomTest.kt
│ │ │ ├── MediaTypeGetTest.kt
│ │ │ ├── MediaTypeJvmTest.kt
│ │ │ ├── MediaTypeTest.kt
│ │ │ ├── MultipartBodyTest.kt
│ │ │ ├── MultipartReaderTest.kt
│ │ │ ├── OkHttpClientTest.kt
│ │ │ ├── OpenJSSETest.kt
│ │ │ ├── ProtocolTest.kt
│ │ │ ├── PublicInternalApiTest.kt
│ │ │ ├── RecordedResponse.kt
│ │ │ ├── RecordingCallback.kt
│ │ │ ├── RecordingExecutor.kt
│ │ │ ├── RequestBodyTest.kt
│ │ │ ├── RequestCommonTest.kt
│ │ │ ├── RequestTest.kt
│ │ │ ├── ResponseBodyJvmTest.kt
│ │ │ ├── ResponseBodyTest.kt
│ │ │ ├── ResponseCommonTest.kt
│ │ │ ├── ResponseJvmTest.kt
│ │ │ ├── RouteFailureTest.kt
│ │ │ ├── ServerTruncatesRequestTest.kt
│ │ │ ├── SessionReuseTest.kt
│ │ │ ├── SocketChannelTest.kt
│ │ │ ├── SocksProxy.kt
│ │ │ ├── SocksProxyTest.kt
│ │ │ ├── TestLogHandler.kt
│ │ │ ├── TestTls13Request.kt
│ │ │ ├── TrailersTest.kt
│ │ │ ├── URLConnectionTest.kt
│ │ │ ├── UrlComponentEncodingTester.kt
│ │ │ ├── UrlComponentEncodingTesterJvm.kt
│ │ │ ├── WebPlatformToAsciiData.kt
│ │ │ ├── WebPlatformToAsciiTest.kt
│ │ │ ├── WebPlatformUrlTest.kt
│ │ │ ├── WebPlatformUrlTestData.kt
│ │ │ ├── WholeOperationTimeoutTest.kt
│ │ │ └── internal/
│ │ │ ├── DoubleInetAddressDns.kt
│ │ │ ├── HostnamesTest.kt
│ │ │ ├── RecordingAuthenticator.kt
│ │ │ ├── TagsTest.kt
│ │ │ ├── UtilTest.kt
│ │ │ ├── authenticator/
│ │ │ │ └── JavaNetAuthenticatorTest.kt
│ │ │ ├── cache/
│ │ │ │ └── DiskLruCacheTest.kt
│ │ │ ├── concurrent/
│ │ │ │ ├── TaskLoggerTest.kt
│ │ │ │ ├── TaskRunnerRealBackendTest.kt
│ │ │ │ └── TaskRunnerTest.kt
│ │ │ ├── connection/
│ │ │ │ ├── ConnectionPoolTest.kt
│ │ │ │ ├── FastFallbackExchangeFinderTest.kt
│ │ │ │ ├── InetAddressOrderTest.kt
│ │ │ │ ├── RetryConnectionTest.kt
│ │ │ │ └── RouteSelectorTest.kt
│ │ │ ├── http/
│ │ │ │ ├── CancelTest.kt
│ │ │ │ ├── ExternalHttp2Example.kt
│ │ │ │ ├── HttpDateTest.kt
│ │ │ │ ├── HttpUpgradesTest.kt
│ │ │ │ ├── SocketFailureTest.kt
│ │ │ │ ├── StatusLineTest.kt
│ │ │ │ └── ThreadInterruptTest.kt
│ │ │ ├── http2/
│ │ │ │ ├── BaseTestHandler.kt
│ │ │ │ ├── FrameLogTest.kt
│ │ │ │ ├── HpackTest.kt
│ │ │ │ ├── Http2ConnectionTest.kt
│ │ │ │ ├── Http2Test.kt
│ │ │ │ ├── HttpOverHttp2Test.kt
│ │ │ │ ├── HuffmanTest.kt
│ │ │ │ ├── MockHttp2Peer.kt
│ │ │ │ └── SettingsTest.kt
│ │ │ ├── idn/
│ │ │ │ ├── IdnaMappingTableTest.kt
│ │ │ │ └── PunycodeTest.kt
│ │ │ ├── io/
│ │ │ │ └── FaultyFileSystem.kt
│ │ │ ├── platform/
│ │ │ │ ├── Jdk8WithJettyBootPlatformTest.kt
│ │ │ │ ├── Jdk9PlatformTest.kt
│ │ │ │ └── PlatformTest.kt
│ │ │ ├── publicsuffix/
│ │ │ │ ├── PublicSuffixListGenerator.kt
│ │ │ │ └── PublicSuffixTesting.jvm.kt
│ │ │ ├── tls/
│ │ │ │ ├── CertificatePinnerChainValidationTest.kt
│ │ │ │ ├── ClientAuthTest.kt
│ │ │ │ └── HostnameVerifierTest.kt
│ │ │ └── ws/
│ │ │ ├── MessageDeflaterInflaterTest.kt
│ │ │ ├── RealWebSocketTest.kt
│ │ │ ├── WebSocketExtensionsTest.kt
│ │ │ ├── WebSocketHttpTest.kt
│ │ │ ├── WebSocketReaderTest.kt
│ │ │ ├── WebSocketRecorder.kt
│ │ │ └── WebSocketWriterTest.kt
│ │ └── resources/
│ │ ├── okhttp3/
│ │ │ └── internal/
│ │ │ └── publicsuffix/
│ │ │ └── NOTICE
│ │ ├── web-platform-test-toascii.json
│ │ └── web-platform-test-urltestdata.txt
│ └── main/
│ └── resources/
│ └── META-INF/
│ └── native-image/
│ └── okhttp/
│ └── okhttp/
│ └── native-image.properties
├── okhttp-bom/
│ └── build.gradle.kts
├── okhttp-brotli/
│ ├── README.md
│ ├── api/
│ │ └── okhttp-brotli.api
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java9/
│ │ │ └── module-info.java
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ └── brotli/
│ │ ├── Brotli.kt
│ │ └── BrotliInterceptor.kt
│ └── test/
│ └── java/
│ └── okhttp3/
│ └── brotli/
│ ├── BrotliInterceptorTest.kt
│ └── BrotliTestMain.kt
├── okhttp-coroutines/
│ ├── Module.md
│ ├── README.md
│ ├── api/
│ │ └── okhttp-coroutines.api
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java9/
│ │ │ └── module-info.java
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ └── coroutines/
│ │ └── ExecuteAsync.kt
│ └── test/
│ └── kotlin/
│ └── okhttp3/
│ └── coroutines/
│ └── ExecuteAsyncTest.kt
├── okhttp-dnsoverhttps/
│ ├── README.md
│ ├── api/
│ │ └── okhttp-dnsoverhttps.api
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java9/
│ │ │ └── module-info.java
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ └── dnsoverhttps/
│ │ ├── BootstrapDns.kt
│ │ ├── DnsOverHttps.kt
│ │ └── DnsRecordCodec.kt
│ └── test/
│ └── java/
│ └── okhttp3/
│ └── dnsoverhttps/
│ ├── DnsOverHttpsTest.kt
│ ├── DnsRecordCodecTest.kt
│ ├── DohProviders.kt
│ └── TestDohMain.kt
├── okhttp-hpacktests/
│ ├── README.md
│ ├── build.gradle.kts
│ └── src/
│ └── test/
│ └── java/
│ └── okhttp3/
│ └── internal/
│ └── http2/
│ ├── HpackDecodeInteropTest.kt
│ ├── HpackDecodeTestBase.kt
│ ├── HpackRoundTripTest.kt
│ └── hpackjson/
│ ├── Case.kt
│ ├── HpackJsonUtil.kt
│ └── Story.kt
├── okhttp-idna-mapping-table/
│ ├── README.md
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── kotlin/
│ │ │ └── okhttp3/
│ │ │ └── internal/
│ │ │ └── idn/
│ │ │ ├── GenerateIdnaMappingTableCode.kt
│ │ │ ├── IdnaMappingTableData.kt
│ │ │ ├── MappedRange.kt
│ │ │ ├── MappingTables.kt
│ │ │ └── SimpleIdnaMappingTable.kt
│ │ └── resources/
│ │ └── okhttp3/
│ │ └── internal/
│ │ └── idna/
│ │ └── IdnaMappingTable.txt
│ └── test/
│ └── kotlin/
│ └── okhttp3/
│ └── internal/
│ └── idn/
│ └── MappingTablesTest.kt
├── okhttp-java-net-cookiejar/
│ ├── README.md
│ ├── api/
│ │ └── okhttp-java-net-cookiejar.api
│ ├── build.gradle.kts
│ └── src/
│ └── main/
│ ├── java9/
│ │ └── module-info.java
│ └── kotlin/
│ └── okhttp3/
│ └── java/
│ └── net/
│ └── cookiejar/
│ └── JavaNetCookieJar.kt
├── okhttp-logging-interceptor/
│ ├── Module.md
│ ├── README.md
│ ├── api/
│ │ └── logging-interceptor.api
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java9/
│ │ │ └── module-info.java
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ └── logging/
│ │ ├── HttpLoggingInterceptor.kt
│ │ └── LoggingEventListener.kt
│ └── test/
│ └── java/
│ └── okhttp3/
│ └── logging/
│ ├── HttpLoggingInterceptorTest.kt
│ └── LoggingEventListenerTest.kt
├── okhttp-osgi-tests/
│ ├── build.gradle.kts
│ └── src/
│ └── test/
│ └── kotlin/
│ └── okhttp3/
│ └── osgi/
│ └── OsgiTest.kt
├── okhttp-sse/
│ ├── Module.md
│ ├── README.md
│ ├── api/
│ │ └── okhttp-sse.api
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java9/
│ │ │ └── module-info.java
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ └── sse/
│ │ ├── EventSource.kt
│ │ ├── EventSourceListener.kt
│ │ ├── EventSources.kt
│ │ └── internal/
│ │ ├── RealEventSource.kt
│ │ └── ServerSentEventReader.kt
│ └── test/
│ └── java/
│ └── okhttp3/
│ └── sse/
│ └── internal/
│ ├── Event.kt
│ ├── EventSourceHttpTest.kt
│ ├── EventSourceRecorder.kt
│ ├── EventSourcesHttpTest.kt
│ └── ServerSentEventIteratorTest.kt
├── okhttp-testing-support/
│ ├── README.md
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── kotlin/
│ │ │ └── okhttp3/
│ │ │ ├── CallEvent.kt
│ │ │ ├── ClientRuleEventListener.kt
│ │ │ ├── ConnectionEvent.kt
│ │ │ ├── DelegatingSSLSession.kt
│ │ │ ├── DelegatingSSLSocket.kt
│ │ │ ├── DelegatingSSLSocketFactory.kt
│ │ │ ├── DelegatingServerSocketFactory.kt
│ │ │ ├── DelegatingSocketFactory.kt
│ │ │ ├── EventListenerAdapter.kt
│ │ │ ├── EventListenerRelay.kt
│ │ │ ├── EventRecorder.kt
│ │ │ ├── FailingCall.kt
│ │ │ ├── FakeDns.kt
│ │ │ ├── FakeProxySelector.kt
│ │ │ ├── FakeSSLSession.kt
│ │ │ ├── ForwardingRequestBody.kt
│ │ │ ├── ForwardingResponseBody.kt
│ │ │ ├── JsseDebugLogging.kt
│ │ │ ├── OkHttpClientTestRule.kt
│ │ │ ├── OkHttpDebugLogging.kt
│ │ │ ├── RecordingConnectionListener.kt
│ │ │ ├── RecordingCookieJar.kt
│ │ │ ├── RecordingHostnameVerifier.kt
│ │ │ ├── SimpleProvider.kt
│ │ │ ├── SpecificHostSocketFactory.kt
│ │ │ ├── TestUtilCommon.kt
│ │ │ ├── TestUtilJvm.kt
│ │ │ ├── TestValueFactory.kt
│ │ │ ├── UppercaseRequestInterceptor.kt
│ │ │ ├── UppercaseResponseInterceptor.kt
│ │ │ ├── internal/
│ │ │ │ ├── RecordingOkAuthenticator.kt
│ │ │ │ ├── concurrent/
│ │ │ │ │ └── TaskFaker.kt
│ │ │ │ ├── duplex/
│ │ │ │ │ ├── AsyncRequestBody.kt
│ │ │ │ │ └── MockSocketHandler.kt
│ │ │ │ └── http/
│ │ │ │ └── RecordingProxySelector.kt
│ │ │ ├── okio/
│ │ │ │ └── LoggingFilesystem.kt
│ │ │ └── testing/
│ │ │ ├── Flaky.kt
│ │ │ ├── PlatformRule.kt
│ │ │ └── PlatformVersion.kt
│ │ └── resources/
│ │ └── META-INF/
│ │ └── proguard/
│ │ └── okhttp3.pro
│ └── test/
│ └── kotlin/
│ └── okhttp3/
│ ├── OkHttpClientTestRuleTest.kt
│ └── testing/
│ └── PlatformRuleTest.kt
├── okhttp-tls/
│ ├── Module.md
│ ├── README.md
│ ├── api/
│ │ └── okhttp-tls.api
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ ├── java9/
│ │ │ └── module-info.java
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ └── tls/
│ │ ├── Certificates.kt
│ │ ├── HandshakeCertificates.kt
│ │ ├── HeldCertificate.kt
│ │ └── internal/
│ │ ├── InsecureAndroidTrustManager.kt
│ │ ├── InsecureExtendedTrustManager.kt
│ │ ├── TlsUtil.kt
│ │ └── der/
│ │ ├── Adapters.kt
│ │ ├── AnyValue.kt
│ │ ├── BasicDerAdapter.kt
│ │ ├── BitString.kt
│ │ ├── Certificate.kt
│ │ ├── CertificateAdapters.kt
│ │ ├── DerAdapter.kt
│ │ ├── DerHeader.kt
│ │ ├── DerReader.kt
│ │ ├── DerWriter.kt
│ │ └── ObjectIdentifiers.kt
│ └── test/
│ └── java/
│ └── okhttp3/
│ └── tls/
│ ├── CertificatesJavaTest.java
│ ├── CertificatesTest.kt
│ ├── HandshakeCertificatesTest.kt
│ ├── HeldCertificateTest.kt
│ └── internal/
│ └── der/
│ ├── DerCertificatesTest.kt
│ └── DerTest.kt
├── okhttp-urlconnection/
│ ├── README.md
│ ├── api/
│ │ └── okhttp-urlconnection.api
│ ├── build.gradle.kts
│ └── src/
│ └── main/
│ ├── java9/
│ │ └── module-info.java
│ └── kotlin/
│ └── okhttp3/
│ ├── JavaNetAuthenticator.kt
│ └── JavaNetCookieJar.kt
├── okhttp-zstd/
│ ├── README.md
│ ├── api/
│ │ └── okhttp-zstd.api
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ └── zstd/
│ │ └── Zstd.kt
│ └── test/
│ └── java/
│ └── okhttp3/
│ └── zstd/
│ ├── ZstdInterceptorJavaTest.java
│ ├── ZstdInterceptorTest.kt
│ └── ZstdTestMain.kt
├── regression-test/
│ ├── README.md
│ ├── build.gradle.kts
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── okhttp/
│ │ └── regression/
│ │ ├── IssueReproductionTest.java
│ │ ├── LetsEncryptTest.java
│ │ └── compare/
│ │ ├── AndroidHttpEngineTest.kt
│ │ ├── ApacheHttpClientHttp2Test.kt
│ │ ├── ApacheHttpClientTest.kt
│ │ └── OkHttpClientTest.java
│ └── main/
│ ├── AndroidManifest.xml
│ └── res/
│ ├── values/
│ │ └── strings.xml
│ └── xml/
│ └── network_security_config.xml
├── samples/
│ ├── compare/
│ │ ├── build.gradle.kts
│ │ └── src/
│ │ └── test/
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ └── compare/
│ │ ├── ApacheHttpClientTest.kt
│ │ ├── JavaHttpClientTest.kt
│ │ ├── JettyHttpClientTest.kt
│ │ └── OkHttpClientTest.kt
│ ├── crawler/
│ │ ├── build.gradle.kts
│ │ └── src/
│ │ └── main/
│ │ └── java/
│ │ └── okhttp3/
│ │ └── sample/
│ │ └── Crawler.java
│ ├── guide/
│ │ ├── README.md
│ │ ├── build.gradle.kts
│ │ └── src/
│ │ ├── main/
│ │ │ └── java/
│ │ │ └── okhttp3/
│ │ │ ├── guide/
│ │ │ │ ├── GetExample.java
│ │ │ │ └── PostExample.java
│ │ │ └── recipes/
│ │ │ ├── AccessHeaders.java
│ │ │ ├── AsynchronousGet.java
│ │ │ ├── Authenticate.java
│ │ │ ├── CacheResponse.java
│ │ │ ├── CancelCall.java
│ │ │ ├── CertificatePinning.java
│ │ │ ├── CheckHandshake.java
│ │ │ ├── ConfigureTimeouts.java
│ │ │ ├── CurrentDateHeader.java
│ │ │ ├── CustomCipherSuites.java
│ │ │ ├── CustomTrust.java
│ │ │ ├── HttpsServer.java
│ │ │ ├── LoggingInterceptors.java
│ │ │ ├── ParseResponseWithMoshi.java
│ │ │ ├── PerCallSettings.java
│ │ │ ├── PostFile.java
│ │ │ ├── PostForm.java
│ │ │ ├── PostMultipart.java
│ │ │ ├── PostStreaming.java
│ │ │ ├── PostStreamingWithPipe.java
│ │ │ ├── PostString.java
│ │ │ ├── PreemptiveAuth.java
│ │ │ ├── PrintEvents.java
│ │ │ ├── PrintEventsNonConcurrent.java
│ │ │ ├── Progress.java
│ │ │ ├── RequestBodyCompression.java
│ │ │ ├── RewriteResponseCacheControl.java
│ │ │ ├── SynchronousGet.java
│ │ │ ├── UploadProgress.java
│ │ │ ├── WebSocketEcho.java
│ │ │ └── kt/
│ │ │ ├── AccessHeaders.kt
│ │ │ ├── AsynchronousGet.kt
│ │ │ ├── Authenticate.kt
│ │ │ ├── CacheResponse.kt
│ │ │ ├── CancelCall.kt
│ │ │ ├── CertificatePinning.kt
│ │ │ ├── ConfigureTimeouts.kt
│ │ │ ├── CustomTrust.kt
│ │ │ ├── DevServer.kt
│ │ │ ├── ParseResponseWithMoshi.kt
│ │ │ ├── PerCallSettings.kt
│ │ │ ├── PostFile.kt
│ │ │ ├── PostForm.kt
│ │ │ ├── PostMultipart.kt
│ │ │ ├── PostPath.kt
│ │ │ ├── PostStreaming.kt
│ │ │ ├── PostString.kt
│ │ │ ├── SynchronousGet.kt
│ │ │ ├── UploadProgress.kt
│ │ │ ├── WiresharkExample.kt
│ │ │ └── YubikeyClientAuth.kt
│ │ └── test/
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ └── AllMainsTest.kt
│ ├── simple-client/
│ │ ├── build.gradle.kts
│ │ └── src/
│ │ └── main/
│ │ └── java/
│ │ └── okhttp3/
│ │ └── sample/
│ │ └── OkHttpContributors.java
│ ├── slack/
│ │ ├── build.gradle.kts
│ │ └── src/
│ │ └── main/
│ │ └── java/
│ │ └── okhttp3/
│ │ └── slack/
│ │ ├── OAuthSession.java
│ │ ├── OAuthSessionFactory.java
│ │ ├── RtmSession.java
│ │ ├── RtmStartResponse.java
│ │ ├── SlackApi.java
│ │ └── SlackClient.java
│ ├── static-server/
│ │ ├── build.gradle.kts
│ │ └── src/
│ │ └── main/
│ │ └── java/
│ │ └── okhttp3/
│ │ └── sample/
│ │ └── SampleServer.java
│ ├── tlssurvey/
│ │ ├── build.gradle.kts
│ │ └── src/
│ │ └── main/
│ │ ├── kotlin/
│ │ │ └── okhttp3/
│ │ │ └── survey/
│ │ │ ├── CipherSuiteSurvey.kt
│ │ │ ├── Clients.kt
│ │ │ ├── Iana.kt
│ │ │ ├── RunSurvey.kt
│ │ │ ├── ssllabs/
│ │ │ │ ├── SslLabsApi.kt
│ │ │ │ ├── SslLabsClient.kt
│ │ │ │ └── UserAgentCapabilities.kt
│ │ │ └── types/
│ │ │ ├── Client.kt
│ │ │ └── SuiteId.kt
│ │ └── resources/
│ │ ├── okhttp_3.11.txt
│ │ ├── okhttp_3.13.txt
│ │ ├── okhttp_3.14.txt
│ │ ├── okhttp_3.9.txt
│ │ └── okhttp_4.10.txt
│ └── unixdomainsockets/
│ ├── build.gradle.kts
│ └── src/
│ └── main/
│ └── java/
│ └── okhttp3/
│ └── unixdomainsockets/
│ ├── ClientAndServer.java
│ ├── TunnelingUnixSocket.java
│ ├── UnixDomainServerSocketFactory.java
│ └── UnixDomainSocketFactory.java
├── settings.gradle.kts
└── test_docs.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .devcontainer/devcontainer.json
================================================
{
"image": "mcr.microsoft.com/devcontainers/java:21-bookworm",
"features": {
"ghcr.io/devcontainers/features/java:1": {
"version": "17"
}
},
"customizations": {
"vscode": {
"settings": {
"java.server.launchMode": "Standard"
},
"extensions": [
"vscjava.vscode-java-pack",
"vscjava.vscode-gradle"
]
}
}
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_size = 2
ij_continuation_indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{kt, kts}]
ij_kotlin_imports_layout = *
================================================
FILE: .gitattributes
================================================
* text=auto eol=lf
*.bat text eol=crlf
*.jar binary
================================================
FILE: .github/CONTRIBUTING.md
================================================
Contributing
============
If you would like to contribute code to OkHttp you can do so through GitHub by
forking the repository and sending a pull request.
When submitting code, please make every effort to follow existing conventions
and style in order to keep the code as readable as possible. Please also make
sure your code compiles by running `./gradlew check`. Checkstyle failures
during compilation indicate errors in your style and can be viewed in the
`checkstyle-result.xml` file.
Some general advice
- Don’t change public API lightly, avoid if possible, and include your reasoning in the PR if essential. It causes pain for developers who use OkHttp and sometimes runtime errors.
- Favour a working external library if appropriate. There are many examples of OkHttp libraries that can sit on top or hook in via existing APIs.
- Get working code on a personal branch with tests before you submit a PR.
- OkHttp is a small and light dependency. Don't introduce new dependencies or major new functionality.
- OkHttp targets the intersection of RFC correct *and* widely implemented. Incorrect implementations that are very widely implemented e.g. a bug in Apache, Nginx, Google, Firefox should also be handled.
Before your code can be accepted into the project you must also sign the
[Individual Contributor License Agreement (CLA)][1].
[1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: A reproducible problem
title: ''
labels: bug
assignees: ''
---
Good bug reports include a failing test! Writing a test helps you to isolate and describe the problem, and it helps us to fix it fast. Bug reports without a failing test or reproduction steps are likely to be closed.
Here’s an example test to get you started.
https://gist.github.com/swankjesse/981fcae102f513eb13ed
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea
title: ''
labels: enhancement
assignees: ''
---
Start by telling us what problem you’re trying to solve. Often a solution already exists!
Don’t send pull requests to implement new features without first getting our support. Sometimes we leave features out on purpose to keep the project small.
================================================
FILE: .github/ISSUE_TEMPLATE/question.md
================================================
---
name: Question
about: Use Stack Overflow instead
title: "\U0001F649"
labels: ''
assignees: ''
---
🛑 𝙎𝙏𝙊𝙋
This issue tracker is not the place for questions!
If you want to ask how to do something, or to understand why something isn't working the way you expect it to, use Stack Overflow. https://stackoverflow.com/questions/tagged/okhttp
We close all questions without reading them.
================================================
FILE: .github/renovate.json
================================================
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"group:monorepos",
"group:recommended",
":dependencyDashboard"
],
"semanticCommits": "disabled",
"labels": [
"renovate"
],
"ignoreDeps": [
"com.squareup.okhttp3:okhttp",
"com.squareup.okhttp3:okhttp-tls",
"com.squareup.okhttp3:mockwebserver"
],
"packageRules": [
{
"groupName": "bnd",
"matchPackageNames": [
"/biz.*/"
]
},
{
"groupName": "graalvm",
"matchPackageNames": [
"/org.graalvm.*/"
]
},
{
"matchPackageNames": [
"org.objenesis:objenesis"
],
"allowedVersions": "<=2.6"
},
{
"extends": [
"monorepo:jetty"
],
"allowedVersions": "<10.0",
"description": "JDK 11 requirement"
},
{
"extends": [
"monorepo:junit5"
],
"allowedVersions": "<5.14.0",
},
{
"matchPackageNames": [
"org.junit-pioneer:junit-pioneer"
],
"allowedVersions": "<2.0.0",
"description": "JDK 11 requirement"
},
{
"matchPackageNames": [
"androidx.activity:activity-ktx"
],
"allowedVersions": "<=1.11.0",
"description": "Android minSdk 23 requirement"
}
]
}
================================================
FILE: .github/workflows/build.yml
================================================
name: build
on:
push:
branches:
- master
pull_request:
types: [opened, labeled, unlabeled, synchronize]
permissions:
contents: read
env:
GRADLE_OPTS: "-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
publish:
runs-on: ubuntu-latest
if: github.repository == 'square/okhttp' && github.ref == 'refs/heads/master'
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Upload Artifacts
run: ./gradlew clean publish --stacktrace
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SECRET_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_SECRET_PASSPHRASE }}
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: gradle/actions/wrapper-validation@v5
- name: Validate Renovate
uses: rinchsan/renovate-config-validator@v0.2.0
with:
pattern: '.github/renovate.json'
checks:
permissions:
checks: write # for mikepenz/action-junit-report
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: |
11
21
- uses: graalvm/setup-graalvm@v1
with:
distribution: 'graalvm'
java-version: 21
github-token: ${{ secrets.GITHUB_TOKEN }}
native-image-job-reports: true
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Run Checks
run: ./gradlew check -PgraalBuild=true -x jvmTest -x test -x allTests -x java9Test
jvm:
permissions:
checks: write # for mikepenz/action-junit-report
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
java-version:
- 8
- 11
- 17
- 21
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: |
${{ matrix.java-version }}
21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Run Tests
run: ./gradlew test allTests -Ptest.java.version=${{ matrix.java-version }}
- name: Publish Test Report
if: github.repository == 'square/okhttp' && github.ref == 'refs/heads/master' && matrix.java-version == '11'
uses: mikepenz/action-junit-report@v6
with:
report_paths: '**/build/test-results/*/TEST-*.xml'
check_name: OpenJDK 11 Test Report
- name: Publish Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: github.repository == 'square/okhttp' && github.ref == 'refs/heads/master' && matrix.java-version == '11'
with:
files: |
**/build/test-results/*/TEST-*.xml
openjdk8alpn:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master' || contains(github.event.pull_request.labels.*.name, 'jdkversions')
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install Old JDK 8
uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: 8.0.242
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Run Tests
run: ./gradlew test allTests -Ptest.java.version=8 -Pokhttp.platform=jdk8alpn -Palpn.boot.version=8.1.13.v20181017 -Dorg.gradle.java.installations.paths=/opt/hostedtoolcache/Java_Adopt_jdk/8.0.242-8.1/x64
providers:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master' || contains(github.event.pull_request.labels.*.name, 'providers')
strategy:
matrix:
include:
- provider: openjsse
java-version: 8
- provider: bouncycastle
java-version: 21
- provider: corretto
java-version: 21
- provider: conscrypt
java-version: 21
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: |
${{ matrix.java-version }}
21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Run Tests
run: ./gradlew test allTests -Pokhttp.platform=${{ matrix.provider }} -Ptest.java.version=${{ matrix.java-version }}
openjdklatest:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDKs
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: |
11
17
24
- name: Allow incompatible JVM versions
run: |
echo 'kotlin.jvm.target.validation.mode=ignore' >> ./gradle.properties
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Run Tests
run: ./gradlew test allTests -Ptest.java.version=24
openjdkearlyaccess:
runs-on: ubuntu-latest
if: false # https://youtrack.jetbrains.com/issue/KTOR-8489
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: |
11
17
25-ea
- name: Allow incompatible JVM versions
run: |
echo 'kotlin.jvm.target.validation.mode=ignore' >> ./gradle.properties
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Run Tests
run: ./gradlew test allTests -Ptest.java.version=25
testwindows:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Run Tests
run: ./gradlew test allTests
graal:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 21
- uses: graalvm/setup-graalvm@v1
with:
distribution: 'graalvm'
java-version: 24
github-token: ${{ secrets.GITHUB_TOKEN }}
native-image-job-reports: true
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Build okcurl
run: ./gradlew okcurl:nativeBuild
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Run native-image tests
run: ./gradlew -PgraalBuild=true native-image-tests:nativeTest
android:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
api-level:
- 21
- 23
- 29
- 34
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 21
- name: Enable KVM group perms
# https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Verify KVM
run: |
sudo apt-get install -y cpu-checker
kvm-ok || echo "KVM is not accelerated"
kvm-ok || exit 1
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Gradle cache
run: ./gradlew :android-test:test
- name: AVD System Image Cache
uses: actions/cache@v5
id: avd-cache
with:
key: avd-${{ runner.os }}-${{ matrix.api-level }}-${{ matrix.api-level >= 30 && 'x86_64' || 'x86' }}
path: |
~/.android/avd/*
~/.android/adb*
# Added the actual system image path
${{ env.ANDROID_HOME }}/system-images/android-${{ matrix.api-level }}
- name: Create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
force-avd-creation: false
arch: ${{ matrix.api-level >= 30 && 'x86_64' || 'x86' }}
# No window, no audio, and use swiftshader for headless environments
emulator-options: >
-no-window
-gpu swiftshader_indirect
-noaudio
-no-boot-anim
-camera-back none
-memory 2048
disable-animations: true
script: echo "Generated AVD snapshot for caching."
- name: Run Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
arch: ${{ matrix.api-level == '34' && 'x86_64' || 'x86' }}
script: ./gradlew -PandroidBuild=true connectedCheck
env:
API_LEVEL: ${{ matrix.api-level }}
- name: Build Release App
run: ./gradlew android-test-app:lint android-test-app:assembleRelease
loom:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: |
21
24
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Run Tests
run: ./gradlew test allTests -Pokhttp.platform=loom -Ptest.java.version=24 -PcontainerTests=true
maven:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Publish local snapshot
run: ./gradlew publishToMavenLocal
- name: Run maven test
working-directory: ./maven-tests
run: ./mvnw -q verify
java9_modules:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 24
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Test module-tests
run: ./gradlew module-tests:test -PokhttpModuleTests=true
- name: Run with Jlink
run: ./gradlew module-tests:imageRun -PokhttpModuleTests=true
================================================
FILE: .github/workflows/containers.yml
================================================
name: containers
on:
push:
branches:
- master
pull_request:
types: [opened, labeled, unlabeled, synchronize]
permissions:
contents: read
env:
GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"
jobs:
test_containers:
permissions:
checks: write # for actions/upload-artifact
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master' || contains(github.event.pull_request.labels.*.name, 'containers')
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Run Container Tests
run: ./gradlew container-tests:test -PcontainerTests=true
================================================
FILE: .github/workflows/docs.yml
================================================
name: docs
on:
push:
branches:
- master
pull_request:
types: [opened, labeled, unlabeled, synchronize]
permissions:
contents: read
env:
GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"
jobs:
test_docs:
permissions:
checks: write # for actions/upload-artifact
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master' || contains(github.event.pull_request.labels.*.name, 'documentation')
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 21
- uses: actions/setup-python@v6
with:
python-version: 3.x
- run: pip install mkdocs-material mkdocs-redirects
- name: Generate Docs
run: ./test_docs.sh
- uses: actions/upload-artifact@v7
with:
name: docs
path: site/
================================================
FILE: .github/workflows/publish.yml
================================================
name: publish
on:
push:
tags:
- '**'
env:
GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false"
jobs:
publish:
runs-on: macos-15
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version-file: .github/workflows/.java-version
- run: ./gradlew publish
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SECRET_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_SECRET_PASSPHRASE }}
================================================
FILE: .gitignore
================================================
.classpath
.kotlin
.project
.settings
.gradle
eclipsebin
bin
gen
build
out
lib
generated
target
pom.xml.*
release.properties
local.properties
.idea
*.iml
*.ipr
*.iws
*.log
classes
obj
.DS_Store
# Special Mkdocs files
docs/5.x
docs/changelog.md
docs/contributing.md
docs/index.md
# jenv
/.java-version
/site/
/docs/changelogs/changelog.md
================================================
FILE: .gitmodules
================================================
[submodule "okhttp-hpacktests/src/test/resources/hpack-test-case"]
path = okhttp-hpacktests/src/test/resources/hpack-test-case
url = https://github.com/http2jp/hpack-test-case.git
================================================
FILE: .junit.run/Not Slow.run.xml
================================================
================================================
FILE: .vscode/settings.json
================================================
{
"java.configuration.updateBuildConfiguration": "interactive",
"java.import.gradle.wrapper.enabled": true
}
================================================
FILE: BUG-BOUNTY.md
================================================
Serious about security
======================
Square recognizes the important contributions the security research community
can make. We therefore encourage reporting security issues with the code
contained in this repository.
If you believe you have discovered a security vulnerability, please follow the
guidelines at https://bugcrowd.com/engagements/blockopensource.
================================================
FILE: CHANGELOG.md
================================================
Change Log
==========
## Version 5.3.2
_2025-11-18_
* Fix: Don't delay triggering timeouts. In Okio 3.16.0 we introduced a regression that caused
timeouts to fire later than they were supposed to.
* Upgrade: [Okio 3.16.4][okio_3_16_4].
## Version 5.3.1
_2025-11-16_
This release is the same as 5.3.0. Okio 3.16.3 didn't have a necessary fix!
* Upgrade: [Okio 3.16.3][okio_3_16_3].
## Version 5.3.0
_2025-10-30_
* New: Add tags to `Call`, including computable tags. Use this to attach application-specific
metadata to a `Call` in an `EventListener` or `Interceptor`. The tag can be read in any other
`EventListener` or `Interceptor`.
```kotlin
override fun intercept(chain: Interceptor.Chain): Response {
chain.call().tag(MyAnalyticsTag::class) {
MyAnalyticsTag(...)
}
return chain.proceed(chain.request())
}
```
* New: Support request bodies on HTTP/1.1 connection upgrades.
* New: `EventListener.plus()` makes it easier to observe events in multiple listeners.
* Fix: Don't spam logs with _‘Method isLoggable in android.util.Log not mocked.’_ when using
OkHttp in Robolectric and Paparazzi tests.
* Upgrade: [Kotlin 2.2.21][kotlin_2_2_21].
* Upgrade: [Okio 3.16.2][okio_3_16_2].
* Upgrade: [ZSTD-KMP 0.4.0][zstd_kmp_0_4_0]. This update fixes a bug that caused APKs to fail
[16 KB ELF alignment checks][elf_alignment].
## Version 5.2.3
_2025-11-18_
* Fix: Don't delay triggering timeouts. In Okio 3.16.0 we introduced a regression that caused
timeouts to fire later than they were supposed to.
* Upgrade: [Okio 3.16.4][okio_3_16_4].
## Version 5.2.2
_2025-11-16_
This release is the same as 5.2.1. Okio 3.16.3 didn't have a necessary fix!
* Upgrade: [Okio 3.16.3][okio_3_16_3].
## Version 5.2.1
_2025-10-09_
* Fix: Don't crash when calling `Socket.shutdownOutput()` or `shutdownInput()` on an `SSLSocket`
on Android API 21 through 23. This method throws an `UnsupportedOperationException`, so we now
catch that and close the underlying stream instead.
* Upgrade: [Okio 3.16.1][okio_3_16_1].
## Version 5.2.0
_2025-10-07_
* New: Support [HTTP 101] responses with `Response.socket`. This mechanism is only supported on
HTTP/1.1. We also reimplemented our websocket client to use this new mechanism.
* New: The `okhttp-zstd` module negotiates [Zstandard (zstd)][zstd] compression with servers that
support it. It integrates a new (unstable) [ZSTD-KMP] library, also from Square. Enable it like
this:
```kotlin
val client = OkHttpClient.Builder()
.addInterceptor(CompressionInterceptor(Zstd, Gzip))
.build()
```
* New: Support the `QUERY` HTTP method. You will need to set the `Request.cacheUrlOverride`
property to cache calls made with this method. The `RequestBody.sha256()` may be helpful here;
use it to compose a cache URL from the query body.
* New: Publish events when calls must wait to execute. `EventListener.dispatcherQueueStart()`
is invoked when a call starts waiting, and `dispatcherQueueEnd()` is invoked when it's done.
* New: `Request.toCurl()` returns a copy-pasteable [curl] command consistent with Chrome’s and
Firefox’s ‘copy as cURL’ features.
* New: Support [JPMS]. We replaced our `Automatic-Module-Name` metadata with proper
`module-info.java` files.
* Fix: Recover gracefully when worker threads are interrupted. When we introduced fast fallback in
OkHttp 5.0, we started using background threads while connecting. Sadly that code didn't handle
interruptions well. This is now fixed.
* Upgrade: [Kotlin 2.2.20][kotlin_2_2_20].
* Upgrade: [Okio 3.16.0][okio_3_16_0].
## Version 5.1.0
_2025-07-07_
* New: `Response.peekTrailers()`. When we changed `Response.trailers()` to block instead of
throwing in 5.0.0, we inadvertently removed the ability for callers to peek the trailers
(by catching the `IllegalStateException` if they weren't available). This new API restores that
capability.
* Fix: Don't crash on `trailers()` if the response doesn't have a body. We broke [Retrofit] users
who read the trailers on the `raw()` OkHttp response, after its body was decoded.
## Version 5.0.0
_2025-07-02_
This is our first stable release of OkHttp since 2023. Here's the highlights if you're upgrading
from OkHttp 4.x:
**OkHttp is now packaged as separate JVM and Android artifacts.** This allows us to offer
platform-specific features and optimizations. If your build system handles [Gradle module metadata],
this change should be automatic.
**MockWebServer has a new coordinate and package name.** We didn’t like that our old artifact
depends on JUnit 4 so the new one doesn’t. It also has a better API built on immutable values. (We
intend to continue publishing the old `okhttp3.mockwebserver` artifact so there’s no urgency to
migrate.)
| Coordinate | Package Name | Description |
|:-------------------------------------------------| :-------------------- | :-------------------------------- |
| com.squareup.okhttp3:mockwebserver3:5.0.0 | mockwebserver3 | Core module. No JUnit dependency! |
| com.squareup.okhttp3:mockwebserver3-junit4:5.0.0 | mockwebserver3.junit4 | Optional JUnit 4 integration. |
| com.squareup.okhttp3:mockwebserver3-junit5:5.0.0 | mockwebserver3.junit5 | Optional JUnit 5 integration. |
| com.squareup.okhttp3:mockwebserver:5.0.0 | okhttp3.mockwebserver | Obsolete. Depends on JUnit 4. |
**OkHttp now supports Happy Eyeballs ([RFC 8305][rfc_8305]) for IPv4+IPv6 networks.** It attempts
both IPv6 and IPv4 connections concurrently, keeping whichever connects first.
**We’ve improved our Kotlin APIs.** You can skip the builder:
```kotlin
val request = Request(
url = "https://cash.app/".toHttpUrl(),
)
```
**OkHttp now supports [GraalVM].**
Here’s what has changed since 5.0.0-alpha.17:
* Upgrade: [Okio 3.15.0][okio_3_15_0].
* Upgrade: [Kotlin 2.2.0][kotlin_2_2_0].
* Fix: Don't crash with a `NoSuchMethodError` when using OkHttp with the Sentry SDK.
* Fix: Retain the query data in the old `okhttp3.mockwebserver.RecordedRequest.path` property. We
inadvertently changed this behavior when we introduced the `mockwebserver3` API.
## Version 5.0.0-alpha.17
_2025-06-29_
This release stabilizes many APIs for the imminent OkHttp 5.0.0 release.
* New: `TrailersSource`, a public API for HTTP trailers. Production callers shouldn't need this
as the API to read response trailers is unchanged. Testers may use this new stable API to
supply trailers for a `Response`.
* New: `Path.asRequestBody()` is now a non-experimental API.
* New: `FileDescriptor.toRequestBody()` is now a non-experimental API.
* New: Stop using experimental coroutines APIs in our `okhttp-coroutines` artifact.
* Breaking: Move `gzip` from `RequestBody` to `Request.Builder`. This new API handles both
compressing the request body and also adding the corresponding `Content-Encoding` header. Note
that this function is sensitive to when it is called: the response body must be supplied before
it can be compressed.
* Breaking: Remove `AddressPolicy`, `AsyncDns`, and `ConnectionListener` from the public API. We
intend to ship a public API for these features, but we don't want to hold OkHttp 5.0.0 until
those APIs are stable.
* Fix: Change `MockWebServer.close()` to cancel ongoing calls that are blocked on a delay.
* Upgrade: [Okio 3.13.0][okio_3_13_0].
This release also stabilizes many APIs in the `mockwebserver3` artifact that's new in 5.0.
* Breaking: `RecordedRequest.body` is now nullable. Null is used when the request does not have a
body.
* Breaking: `RecordedRequest.chunkSizes` is now nullable. Null is used when the request does not
use chunked encoding. This is different from an empty list - that indicates the request is
chunked but has no data.
* Breaking: Replace `SocketPolicy` with a new type, `SocketEffect`. It splits triggers (request
start, response body, etc.) from effects (closing the socket, closing the stream, etc.).
* Breaking: Rename `RecordedRequest.sequenceNumber` to `exchangeIndex` and introduce
`connectionIndex` on that type. These properties may be useful when testing features like
connection reuse.
* Breaking: Replace our parameters-based JUnit 5 extension with a new annotation, `@StartStop`.
Put this annotation on a `MockWebServer` property and the extension will start it before your
test executes and stop it after it completes. No further configuration is required.
```kotlin
@StartStop val server = MockWebServer()
```
* Breaking: Don't automatically start `MockWebServer` after calls to accessors like `port`. Now
these accessors will throw an `IllegalStateException` if the service has not yet been started.
* Breaking: Rename `RecordedRequest.path` to `RecordedRequest.target`. (This property is
_sometimes_ a path, but it can also be a path and query, or a full URL.)
* Breaking: Decompose the `RecordedRequest.requestLine` into three properties, `method`, `target`,
and `version`. This better suits HTTP/2 where the request line had to be synthesized from
component headers.
* Breaking: Change `RecordedRequest.body` from a mutable `Buffer` to an immutable `ByteString`.
* Breaking: Adopt Okio's new `Socket` interface for `MockResponse.socketHandler`.
Note that any _Breaking_ changes above impact only APIs introduced in earlier 5.0.0-alpha releasees.
We don't break binary compatibility with non-alpha APIs.
## Version 5.0.0-alpha.16
_2025-05-29_
* Fix: The previous release would crash when running on Robolectric. We didn't anticipate
running our Android artifact on the JVM platform!
## Version 5.0.0-alpha.15
_2025-05-28_
**This release introduces separate JVM and Android artifacts.** Until now, we've distributed OkHttp
as a JVM library that _detects_ Android capabilities at runtime, but that doesn't offer
Android-specific APIs. With this release we're starting to publish OkHttp as an AAR for Android
users in addition to our existing JAR for JVM users.
This first Android-specific artifact adopts Android's `assets` mechanism to embed the public suffix
data. We will build more Android integration in future releases.
The okhttp-android artifact first introduced in `5.0.0-alpha.7` is no longer available:
* The `AndroidAsyncDns` class moved to the `okhttp` artifact.
* The `AndroidLogging` class is no longer necessary. `LoggingEventListener` and
`HttpLoggingInterceptor` write to logcat by default.
The rest of this release is our highest-quality release yet. Though we continue to use the word
_alpha_ in the version name, the only unstable thing in it is some non-final APIs tagged
`@ExperimentalOkHttpApi`. You can safely use this release in production.
* Fix: Attempt to read the response even if sending the request failed. This makes it possible
to handle response statuses like `HTTP/1.1 431 "Request Header Fields Too Large`.
* Fix: Handle multiple 1xx responses.
* Fix: Address a performance bug in our internal task runner. We had a race condition that could
result in it OkHttp starting a thread for each queued task, even when a single thread could run
all of them.
* Fix: Address a performance bug in `MultipartReader`. We were scanning the entire input stream
for a delimiter when we only needed to scan enough to return a result.
* Fix: Don't double-compress the public suffix database. OkHttp is usually distributed in a
compressed file (like a JAR or APK), so compressing its internal data was redundant.
* Fix: Call `ProxySelector.connectFailed()` when a connection's initial TCP handshake fails.
* Fix: Change the signature of `Dispatcher` to accept a nullable `ExecutorService`. Changing this
parameter to be non-null was an unintended signature change in OkHttp 4.0.
* New: `EventListener.retryDecision()` is called each time a request fails with an `IOException`.
It notifies your listener if OkHttp will retry.
* New: `EventListener.followUpDecision()` is called each time a response is received. It notifies
your listener if OkHttp has decided to make a follow-up request. Some common follow-ups are
authentication challenges and redirects.
* New: Handy constants for `Headers.EMPTY`, `RequestBody.EMPTY`, and `ResponseBody.EMPTY`.
* New: OkHttp now calls `StrictMode.noteSlowCall()` when initializing TLS on Android. Use
`StrictMode` to detect if your `OkHttpClient` is being initialized on the main thread.
* Upgrade: [Okio 3.12.0][okio_3_12_0].
* Upgrade: [Kotlin 2.1.21][kotlin_2_1_21].
* Upgrade: [kotlinx.coroutines 1.10.2][coroutines_1_10_2]. This is used by the optional
`okhttp-coroutines` artifact.
* Upgrade: [AndroidX Startup 1.2.0][startup_1_2_0]. The Android variant of the `okhttp` artifact
now depends on this. This is a new dependency.
* Upgrade: [AndroidX Annotation 1.9.1][annotation_1_9_1]. As above, the Android variant of the
`okhttp` artifact now depends on this. This is also a new dependency.
## Version 5.0.0-alpha.14
_2024-04-17_
* Breaking: Move coroutines extensions to okhttp3.coroutines. Previously this artifact shared the
`okhttp3` package name with our core module, which is incompatible with the Java Platform Module
System.
* Fix in okhttp-coroutines: Publish a valid artifact. The coroutines JAR file in 5.0.0-alpha.13
was corrupt and should not be used.
## Version 5.0.0-alpha.13
_2024-04-16_
* Breaking: Tag unstable new APIs as `@ExperimentalOkHttpApi`. We intend to release OkHttp 5.0
without stabilizing these new APIs first.
Do not use these experimental APIs in modules that may be executed using a version of OkHttp
different from the version that the module was compiled with. Do not use them in published
libraries. Do not use them if you aren't willing to track changes to them.
* Breaking: Drop support for Kotlin Multiplatform.
We planned to support multiplatform in OkHttp 5.0, but after building it, we weren't happy with
the implementation trade-offs. We can't use our HTTP client engine on Kotlin/JS, and we weren't
prepared to build a TLS API for Kotlin/Native.
We'd prefer a multiplatform HTTP client API that's backed by OkHttp on Android and JVM, and
other engines on other platforms. [Ktor] does this pretty well today!
* Breaking: Use `kotlin.time.Duration` in APIs like `OkHttpClient.Builder.callTimeout()`. This
update also drops support for the `DurationUnit` functions introduced in earlier alpha releases
of OkHttp 5.
* Breaking: Reorder the parameters in the Cache constructor that was introduced in 5.0.0-alpha.3.
* New: `Request.Builder.cacheUrlOverride()` customizes the cache key used for a request. This can
be used to make canonical URLs for the cache that omit insignificant query parameters or other
irrelevant data.
This feature may be used with `POST` requests to cache their responses. In such cases the
request body is not used to determine the cache key, so you must manually add cache-relevant
data to the override URL. For example, you could add a `request-body-sha256` query parameter so
requests with the same POST data get the same cache entry.
* New: `HttpLoggingInterceptor.redactQueryParams()` configures the query parameters to redact
in logs. For best security, don't put sensitive information in query parameters.
* New: `ConnectionPool.setPolicy()` configures a minimum connection pool size for a target
address. Use this to proactively open HTTP connections.
Connections opened to fulfill this policy are subject to the connection pool's
`keepAliveDuration` but do not count against the pool-wide `maxIdleConnections` limit.
This feature increases the client's traffic and the load on the server. Talking to your server's
operators before adopting it.
* New in okhttp-android: `HttpLoggingInterceptor.androidLogging()` and
`LoggingEventListener.androidLogging()` write HTTP calls or events to Logcat.
* New: `OkHttpClient.webSocketCloseTimeout` configures how long a web socket connection will wait
for a graceful shutdown before it performs an abrupt shutdown.
* Fix: Honor `RequestBody.isOneShot()` in `MultipartBody`
* Fix in `okhttp-coroutines`: Don't leak response bodies in `executeAsync()`. We had a bug where
we didn't call `Response.close()` if the coroutine was canceled before its response was
returned.
* Upgrade: [Okio 3.9.0][okio_3_9_0].
* Upgrade: [Kotlin 1.9.23][kotlin_1_9_23].
* Upgrade: [Unicode® IDNA 15.1.0][idna_15_1_0]
## Version 5.0.0-alpha.12
_2023-12-17_
We took too long to cut this release and there's a lot of changes in it. We've been busy.
Although this release is labeled _alpha_, the only unstable thing in it is our new APIs. This
release has many critical bug fixes and is safe to run in production. We're eager to stabilize our
new APIs so we can get out of alpha.
* New: Support Java 21's virtual threads (‘OpenJDK Project Loom’). We changed OkHttp's internals
to use `Lock` and `Condition` instead of `synchronized` for best resource utilization.
* New: Switch our Internationalized Domain Name (IDN) implementation to [UTS #46 Nontransitional
Processing][uts46]. With this fix, the `ß` code point no longer maps to `ss`. OkHttp now embeds
its own IDN mapping table in the library.
* New: Prefer the client's configured precedence order for TLS cipher suites. (OkHttp used to
prefer the JDK’s precedence order.) This change may cause your HTTP calls to negotiate a
different cipher suite than before! OkHttp's defaults cipher suites are selected for good
security and performance.
* New: `ConnectionListener` publishes events for connects, disconnects, and use of pooled
connections.
* Fix: Immediately update the connection's flow control window instead of waiting for the
receiving stream to process it.
This change may increase OkHttp's memory use for applications that make many concurrent HTTP
calls and that can receive data faster than they can process it. Previously, OkHttp limited
HTTP/2 to 16 MiB of unacknowledged data per connection. With this fix there is a limit of 16 MiB
of unacknowledged data per stream and no per-connection limit.
* Fix: Don't close a `Deflater` while we're still using it to compress a web socket message. We
had a severe bug where web sockets were closed on the wrong thread, which caused
`NullPointerException` crashes in `Deflater`.
* Fix: Don't crash after a web socket fails its connection upgrade. We incorrectly released
the web socket's connections back to the pool before their resources were cleaned up.
* Fix: Don't infinite loop when a received web socket message has self-terminating compressed
data.
* Fix: Don't fail the call when the response code is ‘HTTP 102 Processing’ or ‘HTTP 103 Early
Hints’.
* Fix: Honor interceptors' changes to connect and read timeouts.
* Fix: Recover gracefully when a cached response is corrupted on disk.
* Fix: Don't leak file handles when a cache disk write fails.
* Fix: Don't hang when the public suffix database cannot be loaded. We had a bug where a failure
reading the public suffix database would cause subsequent reads to hang when they should have
crashed.
* Fix: Avoid `InetAddress.getCanonicalHostName()` in MockWebServer. This avoids problems if the
host machine's IP address has additional DNS registrations.
* New: Create a JPMS-compatible artifact for `JavaNetCookieJar`. Previously, multiple OkHttp
artifacts defined classes in the `okhttp3` package, but this is forbidden by the Java module
system. We've fixed this with a new package (`okhttp3.java.net.cookiejar`) and a new artifact,
`com.squareup.okhttp3:okhttp-java-net-cookiehandler`. (The original artifact now delegates to
this new one.)
```kotlin
implementation("com.squareup.okhttp3:okhttp-java-net-cookiehandler:5.0.0-alpha.12")
```
* New: `Cookie.sameSite` determines whether cookies should be sent on cross-site requests. This
is used by servers to defend against Cross-Site Request Forgery (CSRF) attacks.
* New: Log the total time of the HTTP call in `HttpLoggingInterceptor`.
* New: `OkHttpClient.Builder` now has APIs that use `kotlin.time.Duration`.
* New: `mockwebserver3.SocketPolicy` is now a sealed interface. This is one of several
backwards-incompatible API changes that may impact early adopters of this alpha API.
* New: `mockwebserver3.Stream` for duplex streams.
* New: `mockwebserver3.MockResponseBody` for streamed response bodies.
* New: `mockwebserver3.MockResponse` is now immutable, with a `Builder`.
* New: `mockwebserver3.RecordedRequest.handshakeServerNames` returns the SNI (Server Name
Indication) attribute from the TLS handshake.
* Upgrade: [Kotlin 1.9.21][kotlin_1_9_21].
* Upgrade: [Okio 3.7.0][okio_3_7_0].
## Version 5.0.0-alpha.11
_2022-12-24_
* New: Enable fast fallback by default. It's our implementation of Happy Eyeballs,
[RFC 8305][rfc_8305]. Disable with `OkHttpClient.Builder.fastFallback(false)`.
* Fix: Don't log response bodies for server-sent events.
* Fix: Skip early hints (status code 103) responses.
* Fix: Don't log sensitive headers in `Request.toString()`.
* Fix: Don't crash when the dispatcher's `ExecutorService` is shutdown with many
calls still enqueued.
* Upgrade: [GraalVM 22][graalvm_22].
* Upgrade: [Kotlin 1.7.10][kotlin_1_7_10].
## Version 5.0.0-alpha.10
_2022-06-26_
* Fix: Configure the multiplatform artifact (`com.squareup.okhttp3:okhttp:3.x.x`) to depend on the
JVM artifact (`com.squareup.okhttp3:okhttp-jvm:3.x.x`) for Maven builds. This should work-around
an issue where Maven doesn't interpret Gradle metadata.
* Fix: Make another attempt at supporting Kotlin 1.5.31 at runtime. We were crashing on
`DurationUnit` which was a typealias in 1.5.x.
* Upgrade: [Okio 3.2.0][okio_3_2_0].
## Version 5.0.0-alpha.9
_2022-06-16_
* New: Enforce label length limits in URLs. `HttpUrl` now rejects URLs whose domains aren't valid.
This includes overly-long domain names (longer than 253 characters), overly-long labels (more
than 63 characters between dots), and empty labels.
* New: Don't include the `Content-Length` header in multipart bodies. Servers must delimit
OkHttp's request bodies using the boundary only. (This change makes OkHttp more consistent with
browsers and other HTTP clients.)
* New: Drop the `tunnelProxy` argument in `MockWebServer.useHttps()`. This change only impacts
the OkHttp 5.x API which uses the `mockwebserver3` package.
* Fix: Don't call `toDuration()` which isn't available in kotlin-stdlib 1.4.
## Version 5.0.0-alpha.8
_2022-06-08_
* Fix: Change how `H2_PRIOR_KNOWLEDGE` works with HTTP proxies. Previously OkHttp assumed the
proxy itself was a prior knowledge HTTP/2 server. With this update, OkHttp attempts a `CONNECT`
tunnel just as it would with HTTPS. For prior knowledge with proxies OkHttp's is now consistent
with these curl arguments:
```
curl \
--http2-prior-knowledge \
--proxy localhost:8888 \
--proxytunnel \
http://squareup.com/robots.txt
```
* Fix: Support executing OkHttp on kotlin-stdlib versions as old as 1.4. The library still builds
on up-to-date Kotlin releases (1.6.21) but no longer needs that version as a runtime dependency.
This should make it easier to use OkHttp in Gradle plugins.
* Fix: Don't start the clock on response timeouts until the request body is fully transmitted.
This is only relevant for duplex request bodies, because they are written concurrently when
reading the response body.
* New: `MockResponse.inTunnel()` is a new `mockwebserver3` API to configure responses that are
served while creating a proxy tunnel. This obsoletes both the `tunnelProxy` argument on
`MockWebServer` and the `UPGRADE_TO_SSL_AT_END` socket option. (Only APIs on `mockwebserver3`
are changed; the old `okhttp3.mockwebserver` APIs remain as they always have been.
## Version 5.0.0-alpha.7
_2022-04-26_
**This release introduces new Kotlin-friendly APIs.** When we migrated OkHttp from Java to Kotlin in
OkHttp 4.0, we kept our Java-first APIs. With 5.0 we're continuing to support Java and adding
additional improvements for Kotlin users. In this alpha we're excited to skip-the-builder for
requests and remove a common source of non-null assertions (`!!`) on the response body.
The alpha releases in the 5.0.0 series have production-quality code and an unstable API. We expect
to make changes to the APIs introduced in 5.0.0-alpha.X. These releases are safe for production use
and 'alpha' strictly signals that we're still experimenting with some new APIs. If you're eager for
the fixes or features below, please upgrade.
* New: Named and default parameters constructor for `Request`:
```
val request = Request(
url = "https://cash.app/".toHttpUrl(),
)
```
* New: `Response.body` is now non-null. This was generally the case in OkHttp 4.x, but the Kotlin
type declaration was nullable to support rare cases like the body on `Response.cacheResponse`,
`Response.networkResponse`, and `Response.priorResponse`. In such cases the body is now
non-null, but attempts to read its content will fail.
* New: Kotlin-specific APIs for request tags. Kotlin language users can lookup tags with a type
parameter only, like `request.tag()`.
* New: MockWebServer has improved support for HTTP/1xx responses. Once you've migrated to the new
`mockwebserver3` package, there's a new field, `MockResponse.informationalResponses`.
* Fix: Don't interpret trailers as headers after an HTTP/100 response. This was a bug only when
the HTTP response body itself is empty.
* Fix: Don't crash when a fast fallback call has both a deferred connection and a held connection.
* Fix: `OkHttpClient` no longer implements `Cloneable`. It never should have; the class is
immutable. This is left over from OkHttp 2.x (!) when that class was mutable. We're using the
5.x upgrade as an opportunity to remove very obsolete APIs.
* Fix: Recover gracefully when Android's `NativeCrypto` crashes with `"ssl == null"`. This occurs
when OkHttp retrieves ALPN state on a closed connection.
* Upgrade: [Kotlin 1.6.21][kotlin_1_6_21].
* Upgrade: [Okio 3.1.0][okio_3_1_0].
## Version 5.0.0-alpha.6
_2022-03-14_
* Fix: Don't attempt to close pooled connections. We saw occasional fast fallback calls crash in
the previous alpha due to an unexpected race.
## Version 5.0.0-alpha.5
_2022-02-21_
* Fix: Don't include [Assertk][assertk] in OkHttp's production dependencies. This regression was
introduced in the 5.0.0-alpha.4 release.
* Fix: Don't ask `Dns` implementations to resolve strings that are already IP addresses.
* Fix: Change fast fallback to race TCP handshakes only. To avoid wasted work, OkHttp will not
attempt multiple TLS handshakes for the same call concurrently.
* Fix: Don't crash loading the public suffix database in GraalVM native images. The function
`HttpUrl.topPrivateDomain()` uses a resource file to identify private domains, but we didn't
include this file on GraalVM.
## Version 5.0.0-alpha.4
_2022-02-01_
**This release introduces fast fallback to better support mixed IPv4+IPv6 networks.** Fast fallback
is what we're calling our implementation of Happy Eyeballs, [RFC 8305][rfc_8305]. With this
feature OkHttp will attempt both IPv6 and IPv4 connections concurrently, keeping whichever connects
first. Fast fallback gives IPv6 connections a 250 ms head start so IPv6 is preferred on networks
where it's available.
To opt-in, configure your `OkHttpClient.Builder`:
```
OkHttpClient client = new OkHttpClient.Builder()
.fastFallback(true)
.build();
```
* New: Change the build from Kotlin-JVM to Kotlin-multiplatform (which includes JVM). Both
native and JavaScript platforms are unstable preview releases and subject to
backwards-incompatible changes in forthcoming releases.
* Fix: Don't crash loading the public suffix database resource in obfuscated builds.
* Fix: Don't silently ignore calls to `EventSource.cancel()` made from
`EventSourceListener.onOpen()`.
* Fix: Enforce the max intermediates constraint when using pinned certificates with Conscrypt.
This impacts Conscrypt when the server's presented certificates form both a trusted-but-unpinned
chain and an untrusted-but-pinned chain.
* Upgrade: [Kotlin 1.6.10][kotlin_1_6_10].
## Version 5.0.0-alpha.3
_2021-11-22_
* Fix: Change `Headers.toString()` to redact authorization and cookie headers.
* Fix: Don't do DNS to get the hostname for `RecordedRequest.requestUrl`. This was doing a DNS
lookup for the local hostname, but we really just wanted the `Host` header.
* Fix: Don't crash with a `InaccessibleObjectException` when detecting the platform trust manager
on Java 17+.
* Fix: Don't crash if a cookie's value is a lone double quote character.
* Fix: Don't crash when canceling an event source created by `EventSources.processResponse()`.
* New: `Cache` now has a public constructor that takes an [okio.FileSystem]. This should make it
possible to implement decorators for cache encryption or compression.
* New: `Cookie.newBuilder()` to build upon an existing cookie.
* New: Use TLSv1.3 when running on JDK 8u261 or newer.
* New: `QueueDispatcher.clear()` may be used to reset a MockWebServer instance.
* New: `FileDescriptor.toRequestBody()` may be particularly useful for users of Android's Storage
Access Framework.
* Upgrade: [Kotlin 1.5.31][kotlin_1_5_31].
* Upgrade: [Okio 3.0.0][okio_3_0_0].
## Version 5.0.0-alpha.2
_2021-01-30_
**In this release MockWebServer has a new Maven coordinate and package name.** A longstanding
problem with MockWebServer has been its API dependency on JUnit 4. We've reorganized things to
remove that dependency while preserving backwards compatibility.
| Maven Coordinate | Package Name | Description |
| :------------------------------------------------------- | :-------------------- | :-------------------------------- |
| com.squareup.okhttp3:mockwebserver3:5.0.0-alpha.2 | mockwebserver3 | Core module. No JUnit dependency! |
| com.squareup.okhttp3:mockwebserver3-junit4:5.0.0-alpha.2 | mockwebserver3.junit4 | Optional JUnit 4 integration. |
| com.squareup.okhttp3:mockwebserver3-junit5:5.0.0-alpha.2 | mockwebserver3.junit5 | Optional JUnit 5 integration. |
| com.squareup.okhttp3:mockwebserver:5.0.0-alpha.2 | okhttp3.mockwebserver | Obsolete. Depends on JUnit 4. |
The new APIs use `mockwebserver3` in both the Maven coordinate and package name. This new API is
**not stable** and will likely change before the final 5.0.0 release.
If you have code that subclasses `okhttp3.mockwebserver.QueueDispatcher`, this update is not source
or binary compatible. Migrating to the new `mockwebserver3` package will fix this problem.
* New: DNS over HTTPS is now a stable feature of OkHttp. We introduced this as an experimental
module in 2018. We are confident in its stable API and solid implementation.
* Fix: Work around a crash in Android 10 and 11 that may be triggered when two threads
concurrently close an SSL socket. This would have appeared in crash logs as
`NullPointerException: bio == null`.
* Fix: Use plus `+` instead of `%20` to encode space characters in `FormBody`. This was a
longstanding bug in OkHttp. The fix makes OkHttp consistent with major web browsers.
* Fix: Don't crash if Conscrypt returns a null version.
* Fix: Include the public suffix data as a resource in GraalVM native images.
* Fix: Fail fast when the cache is corrupted.
* Fix: Fail fast when a private key cannot be encoded.
* Fix: Fail fast when attempting to verify a non-ASCII hostname.
* Upgrade: [GraalVM 21][graalvm_21].
* Upgrade: [Kotlin 1.4.20][kotlin_1_4_20].
## Version 5.0.0-alpha.1
_2021-01-30_
**This release adds initial support for [GraalVM][graalvm].**
GraalVM is an exciting new platform and we're eager to adopt it. The startup time improvements over
the JVM are particularly impressive. Try it with okcurl:
```
$ ./gradlew okcurl:nativeImage
$ ./okcurl/build/graal/okcurl https://cash.app/robots.txt
```
This is our first release that supports GraalVM. Our code on this platform is less mature than JVM
and Android! Please report any issues you encounter: we'll fix them urgently.
* Fix: Attempt to read the response body even if the server canceled the request. This will cause
some calls to return nice error codes like `HTTP/1.1 429 Too Many Requests` instead of transport
errors like `SocketException: Connection reset` and `StreamResetException: stream was reset:
CANCEL`.
* New: Support OSGi metadata.
* Upgrade: [Okio 2.9.0][okio_2_9_0].
```kotlin
implementation("com.squareup.okio:okio:2.9.0")
```
Note that this was originally released on 2020-10-06 as 4.10.0-RC1. The only change from that
release is the version name.
## Version 4.x
See [4.x Change log](https://square.github.io/okhttp/changelogs/changelog_4x/) for the legacy version changelogs.
[GraalVM]: https://www.graalvm.org/
[Gradle module metadata]: https://docs.gradle.org/current/userguide/publishing_gradle_module_metadata.html
[HTTP 101]: https://httpwg.org/specs/rfc9110.html#status.101
[JPMS]: https://openjdk.org/projects/jigsaw/spec/
[Ktor]: https://ktor.io/
[Retrofit]: https://square.github.io/retrofit/
[ZSTD-KMP]: https://github.com/square/zstd-kmp
[androidx_startup]: https://developer.android.com/jetpack/androidx/releases/startup
[annotation_1_9_1]: https://developer.android.com/jetpack/androidx/releases/annotation#annotation-1.9.1
[assertk]: https://github.com/willowtreeapps/assertk
[coroutines_1_10_2]: https://github.com/Kotlin/kotlinx.coroutines/releases/tag/1.10.2
[curl]: https://curl.se/
[elf_alignment]: https://developer.android.com/guide/practices/page-sizes
[graalvm]: https://www.graalvm.org/
[graalvm_21]: https://www.graalvm.org/release-notes/21_0/
[graalvm_22]: https://www.graalvm.org/release-notes/22_2/
[idna_15_1_0]: https://www.unicode.org/reports/tr46/#Modifications
[kotlin_1_4_20]: https://github.com/JetBrains/kotlin/releases/tag/v1.4.20
[kotlin_1_5_31]: https://github.com/JetBrains/kotlin/releases/tag/v1.5.31
[kotlin_1_6_10]: https://github.com/JetBrains/kotlin/releases/tag/v1.6.10
[kotlin_1_6_21]: https://github.com/JetBrains/kotlin/releases/tag/v1.6.21
[kotlin_1_7_10]: https://github.com/JetBrains/kotlin/releases/tag/v1.7.10
[kotlin_1_9_21]: https://github.com/JetBrains/kotlin/releases/tag/v1.9.21
[kotlin_1_9_23]: https://github.com/JetBrains/kotlin/releases/tag/v1.9.23
[kotlin_2_1_21]: https://github.com/JetBrains/kotlin/releases/tag/v2.1.21
[kotlin_2_2_0]: https://github.com/JetBrains/kotlin/releases/tag/v2.2.0
[kotlin_2_2_20]: https://github.com/JetBrains/kotlin/releases/tag/v2.2.20
[kotlin_2_2_21]: https://github.com/JetBrains/kotlin/releases/tag/v2.2.21
[loom]: https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html
[okio_2_9_0]: https://square.github.io/okio/changelog/#version-290
[okio_3_0_0]: https://square.github.io/okio/changelog/#version-300
[okio_3_12_0]: https://square.github.io/okio/changelog/#version-3120
[okio_3_13_0]: https://square.github.io/okio/changelog/#version-3130
[okio_3_15_0]: https://square.github.io/okio/changelog/#version-3150
[okio_3_16_0]: https://square.github.io/okio/changelog/#version-3160
[okio_3_16_1]: https://square.github.io/okio/changelog/#version-3161
[okio_3_16_2]: https://square.github.io/okio/changelog/#version-3162
[okio_3_16_3]: https://square.github.io/okio/changelog/#version-3163
[okio_3_16_4]: https://square.github.io/okio/changelog/#version-3164
[okio_3_1_0]: https://square.github.io/okio/changelog/#version-310
[okio_3_2_0]: https://square.github.io/okio/changelog/#version-320
[okio_3_7_0]: https://square.github.io/okio/changelog/#version-370
[okio_3_9_0]: https://square.github.io/okio/changelog/#version-390
[rfc_8305]: https://tools.ietf.org/html/rfc8305
[startup_1_2_0]: https://developer.android.com/jetpack/androidx/releases/startup#1.2.0
[uts46]: https://www.unicode.org/reports/tr46
[zstd]: https://github.com/facebook/zstd
[zstd_kmp_0_4_0]: https://github.com/square/zstd-kmp/blob/main/CHANGELOG.md#version-040
================================================
FILE: CONTRIBUTING.md
================================================
Contributing
============
Keeping the project small and stable limits our ability to accept new contributors. We are not
seeking new committers at this time, but some small contributions are welcome.
If you've found a security problem, please follow our [bug bounty][security] program.
If you've found a bug, please contribute a failing test case so we can study and fix it.
If you have a new feature idea, please build it in an external library. There are
[many libraries][works_with_okhttp] that sit on top or hook in via existing APIs. If you build
something that integrates with OkHttp, tell us so that we can link it!
Before code can be accepted all contributors must complete our
[Individual Contributor License Agreement (CLA)][cla].
Code Contributions
------------------
Get working code on a personal branch with tests passing before you submit a PR:
```
./gradlew clean check
```
Please make every effort to follow existing conventions and style in order to keep the code as
readable as possible.
Contribute code changes through GitHub by forking the repository and sending a pull request. We
squash all pull requests on merge.
Gradle Setup
------------
```
$ cat local.properties
sdk.dir=PATH_TO_ANDROID_HOME/sdk
org.gradle.caching=true
```
Running Android Tests
---------------------
$ ANDROID_SDK_ROOT=PATH_TO_ANDROID_HOME/sdk ./gradlew :android-test:connectedCheck -PandroidBuild=true
Committer's Guides
------------------
* [Concurrency][concurrency]
* [Debug Logging][debug_logging]
* [Releasing][releasing]
[cla]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
[concurrency]: https://square.github.io/okhttp/concurrency/
[debug_logging]: https://square.github.io/okhttp/debug_logging/
[releasing]: https://square.github.io/okhttp/releasing/
[security]: https://square.github.io/okhttp/security/
[works_with_okhttp]: https://square.github.io/okhttp/works_with_okhttp/
[okhttp_build]: https://github.com/square/okhttp/blob/master/okhttp/build.gradle
================================================
FILE: LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
OkHttp
======
See the [project website][okhttp] for documentation and APIs.
HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP
efficiently makes your stuff load faster and saves bandwidth.
OkHttp is an HTTP client that’s efficient by default:
* HTTP/2 support allows all requests to the same host to share a socket.
* Connection pooling reduces request latency (if HTTP/2 isn’t available).
* Transparent GZIP shrinks download sizes.
* Response caching avoids the network completely for repeat requests.
OkHttp perseveres when the network is troublesome: it will silently recover from common connection
problems. If your service has multiple IP addresses, OkHttp will attempt alternate addresses if the
first connect fails. This is necessary for IPv4+IPv6 and services hosted in redundant data
centers. OkHttp supports modern TLS features (TLS 1.3, ALPN, certificate pinning). It can be
configured to fall back for broad connectivity.
Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It
supports both synchronous blocking calls and async calls with callbacks.
A well behaved user agent
-------------------------
OkHttp follows modern HTTP specifications such as
* HTTP Semantics - [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110)
* HTTP Caching- [RFC 9111](https://datatracker.ietf.org/doc/html/rfc9111)
* HTTP/1.1 - [RFC 9112](https://datatracker.ietf.org/doc/html/rfc9112)
* HTTP/2 - [RFC 9113](https://datatracker.ietf.org/doc/html/rfc9113)
* Websockets - [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455)
* SSE - [Server-sent events](https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events)
Where the spec is ambiguous, OkHttp follows modern user agents such as popular Browsers or common HTTP Libraries.
OkHttp is principled and avoids being overly configurable, especially when such configuration is
to workaround a buggy server, test invalid scenarios or that contradict the relevant RFC.
Other HTTP libraries exist that fill that gap allowing extensive customisation including potentially
invalid requests.
Example Limitations
* Does not allow GET with a body.
* Cache is not an interface with alternative implementations.
Get a URL
---------
This program downloads a URL and prints its contents as a string. [Full source][get_example].
```java
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
```
Post to a Server
----------------
This program posts data to a service. [Full source][post_example].
```java
public static final MediaType JSON = MediaType.get("application/json");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
```
Further examples are on the [OkHttp Recipes page][recipes].
Requirements
------------
OkHttp works on Android 5.0+ (API level 21+) and Java 8+.
On Android, OkHttp uses [AndroidX Startup][androidx_startup]. If you disable the initializer in the manifest,
then apps are responsible for calling `OkHttp.initialize(applicationContext)` in `Application.onCreate`.
OkHttp depends on [Okio][okio] for high-performance I/O and the [Kotlin standard library][kotlin]. Both are small libraries with strong backward-compatibility.
We highly recommend you keep OkHttp up-to-date. As with auto-updating web browsers, staying current
with HTTPS clients is an important defense against potential security problems. [We
track][tls_history] the dynamic TLS ecosystem and adjust OkHttp to improve connectivity and
security.
OkHttp uses your platform's built-in TLS implementation. On Java platforms OkHttp also supports
[Conscrypt][conscrypt], which integrates [BoringSSL](https://github.com/google/boringssl) with Java. OkHttp will use Conscrypt if it is
the first security provider:
```java
Security.insertProviderAt(Conscrypt.newProvider(), 1);
```
The OkHttp `3.12.x` branch supports Android 2.3+ (API level 9+) and Java 7+. These platforms lack
support for TLS 1.2 and should not be used.
Releases
--------
Our [change log][changelog] has release history.
The latest release is available on [Maven Central](https://search.maven.org/artifact/com.squareup.okhttp3/okhttp/5.3.0/jar).
```kotlin
implementation("com.squareup.okhttp3:okhttp:5.3.0")
```
Snapshot builds are [available][snap]. [R8 and ProGuard][r8_proguard] rules are available.
Also, we have a [bill of materials (BOM)][bom] available to help you keep OkHttp artifacts up to date and be sure about version compatibility.
```kotlin
dependencies {
// define a BOM and its version
implementation(platform("com.squareup.okhttp3:okhttp-bom:5.3.0"))
// define any required OkHttp artifacts without version
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")
}
```
Maven and JVM Projects
----------------------
OkHttp is published as a Kotlin Multiplatform project. While Gradle handles this automatically,
Maven projects must select between `okhttp-jvm` and `okhttp-android`. The `okhttp` artifact will be empty in
Maven projects.
```xml
com.squareup.okhttp3okhttp-bom5.2.0pomimport
```
```xml
com.squareup.okhttp3okhttp-jvm5.1.0com.squareup.okhttp3mockwebserver3com.squareup.okhttp3logging-interceptor
```
MockWebServer
-------------
OkHttp includes a library for testing HTTP, HTTPS, and HTTP/2 clients.
The latest release is available on [Maven Central](https://search.maven.org/artifact/com.squareup.okhttp3/mockwebserver/5.3.0/jar).
```kotlin
testImplementation("com.squareup.okhttp3:mockwebserver3:5.3.0")
```
MockWebServer is used for firstly for internal testing, and for basic testing of apps using OkHttp client.
It is not a full featured HTTP testing library that is developed standalone. It is not being actively developed
for new features. As such you might find your needs outgrow MockWebServer and you may which to use a
more full featured testing library such as [MockServer](https://www.mock-server.com/).
GraalVM Native Image
--------------------
Building your native images with [GraalVM] should work automatically.
See the okcurl module for an example build.
```shell
$ ./gradlew okcurl:nativeImage
$ ./okcurl/build/graal/okcurl https://httpbin.org/get
```
Java Modules
------------
OkHttp (5.2+) implements Java 9 Modules.
With this in place Java builds should fail if apps attempt to use internal packages.
```
error: package okhttp3.internal.platform is not visible
okhttp3.internal.platform.Platform.get();
^
(package okhttp3.internal.platform is declared in module okhttp3,
which does not export it to module com.bigco.sdk)
```
The stable public API is based on the list of defined modules:
- okhttp3
- okhttp3.brotli
- okhttp3.coroutines
- okhttp3.dnsoverhttps
- okhttp3.java.net.cookiejar
- okhttp3.logging
- okhttp3.sse
- okhttp3.tls
- okhttp3.urlconnection
- mockwebserver3
- mockwebserver3.junit4
- mockwebserver3.junit5
License
-------
```
Copyright 2019 Square, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
[GraalVM]: https://www.graalvm.org/
[androidx_startup]: https://developer.android.com/jetpack/androidx/releases/startup
[bom]: https://docs.gradle.org/6.2/userguide/platforms.html#sub:bom_import
[changelog]: https://square.github.io/okhttp/changelog/
[conscrypt]: https://github.com/google/conscrypt/
[get_example]: https://raw.github.com/square/okhttp/master/samples/guide/src/main/java/okhttp3/guide/GetExample.java
[kotlin]: https://kotlinlang.org/
[okhttp3_pro]: https://raw.githubusercontent.com/square/okhttp/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro
[okhttp]: https://square.github.io/okhttp/
[okhttp_312x]: https://github.com/square/okhttp/tree/okhttp_3.12.x
[okio]: https://github.com/square/okio
[post_example]: https://raw.github.com/square/okhttp/master/samples/guide/src/main/java/okhttp3/guide/PostExample.java
[r8_proguard]: https://square.github.io/okhttp/features/r8_proguard/
[recipes]: https://square.github.io/okhttp/recipes/
[snap]: https://s01.oss.sonatype.org/content/repositories/snapshots/
[tls_history]: https://square.github.io/okhttp/tls_configuration_history/
================================================
FILE: android-test/build.gradle.kts
================================================
import okhttp3.buildsupport.androidBuild
plugins {
id("okhttp.base-conventions")
id("com.android.library")
id("de.mannodermaus.android-junit5")
}
android {
compileSdk = 36
namespace = "okhttp.android.test"
defaultConfig {
minSdk = 21
// Make sure to use the AndroidJUnitRunner (or a sub-class) in order to hook in the JUnit 5 Test Builder
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments += mapOf(
"runnerBuilder" to "de.mannodermaus.junit5.AndroidJUnit5Builder",
"notPackage" to "org.bouncycastle",
"configurationParameters" to "junit.jupiter.extensions.autodetection.enabled=true"
)
}
if (androidBuild) {
sourceSets["androidTest"].java.srcDirs(
"../okhttp-brotli/src/test/java",
"../okhttp-dnsoverhttps/src/test/java",
"../okhttp-logging-interceptor/src/test/java",
"../okhttp-sse/src/test/java"
)
}
compileOptions {
targetCompatibility(JavaVersion.VERSION_11)
sourceCompatibility(JavaVersion.VERSION_11)
}
testOptions {
targetSdk = 34
unitTests.isIncludeAndroidResources = true
}
// issue merging due to conflict with httpclient and something else
packagingOptions.resources.excludes += setOf(
"META-INF/DEPENDENCIES",
"META-INF/LICENSE.md",
"META-INF/LICENSE-notice.md",
"README.txt",
"org/bouncycastle/LICENSE",
"META-INF/versions/9/OSGI-INF/MANIFEST.MF"
)
}
dependencies {
implementation(libs.kotlin.reflect)
implementation(libs.playservices.safetynet)
"friendsImplementation"(projects.okhttp)
"friendsImplementation"(projects.okhttpDnsoverhttps)
testImplementation(projects.okhttp)
testImplementation(libs.junit)
testImplementation(libs.junit.ktx)
testImplementation(libs.assertk)
testImplementation(projects.okhttpTls)
"friendsTestImplementation"(projects.loggingInterceptor)
testImplementation(libs.androidx.test.runner)
testImplementation(libs.robolectric)
testImplementation(libs.androidx.espresso.core)
testImplementation(libs.square.okio.fakefilesystem)
testImplementation(projects.okhttpTestingSupport)
testImplementation(libs.conscrypt.openjdk)
testImplementation(libs.junit.jupiter.engine)
testImplementation(libs.junit.vintage.engine)
androidTestImplementation(projects.okhttpTestingSupport) {
exclude("org.openjsse", "openjsse")
exclude("org.conscrypt", "conscrypt-openjdk-uber")
exclude("software.amazon.cryptools", "AmazonCorrettoCryptoProvider")
}
androidTestImplementation(libs.assertk)
androidTestImplementation(libs.bouncycastle.bcprov)
androidTestImplementation(libs.bouncycastle.bctls)
androidTestImplementation(libs.conscrypt.android)
androidTestImplementation(projects.mockwebserver3Junit4)
androidTestImplementation(projects.mockwebserver3Junit5)
androidTestImplementation(projects.okhttpBrotli)
androidTestImplementation(projects.okhttpZstd)
androidTestImplementation(projects.okhttpDnsoverhttps)
androidTestImplementation(projects.loggingInterceptor)
androidTestImplementation(projects.okhttpSse)
androidTestImplementation(projects.okhttpTls)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.http.client5)
androidTestImplementation(libs.kotlin.test.common)
androidTestImplementation(libs.kotlin.test.junit)
androidTestImplementation(libs.square.moshi)
androidTestImplementation(libs.square.moshi.kotlin)
androidTestImplementation(libs.square.okio.fakefilesystem)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.junit.jupiter.api)
androidTestImplementation(libs.junit5android.core)
androidTestRuntimeOnly(libs.junit5android.runner)
}
junitPlatform {
filters {
excludeTags("Remote")
}
}
================================================
FILE: android-test/src/androidDeviceTest/README.md
================================================
Android Test
============
A gradle module for running Android instrumentation tests on a device or emulator.
1. Add an Emulator named `pixel5`, if you don't already have one
```
$ sdkmanager --install "system-images;android-29;google_apis;x86"
$ echo "no" | avdmanager --verbose create avd --force --name "pixel5" --device "pixel" --package "system-images;android-29;google_apis;x86" --tag "google_apis" --abi "x86"
```
2. Run an Emulator using Android Studio or from command line.
```
$ emulator -no-window -no-snapshot-load @pixel5
```
2. Turn on logs with logcat
```
$ adb logcat '*:E' OkHttp:D Http2:D TestRunner:D TaskRunner:D OkHttpTest:D GnssHAL_GnssInterface:F DeviceStateChecker:F memtrack:F
...
01-01 12:53:32.811 10999 11089 D OkHttp : [49 ms] responseHeadersEnd: Response{protocol=h2, code=200, message=, url=https://1.1.1.1/dns-query?dns=AAABAAABAAAAAAAAA3d3dwhmYWNlYm9vawNjb20AABwAAQ}
01-01 12:53:32.811 10999 11089 D OkHttp : [49 ms] responseBodyStart
01-01 12:53:32.811 10999 11089 D OkHttp : [49 ms] responseBodyEnd: byteCount=128
01-01 12:53:32.811 10999 11089 D OkHttp : [49 ms] connectionReleased
01-01 12:53:32.811 10999 11089 D OkHttp : [49 ms] callEnd
01-01 12:53:32.816 10999 11090 D OkHttp : [54 ms] responseHeadersStart
01-01 12:53:32.816 10999 11090 D OkHttp : [54 ms] responseHeadersEnd: Response{protocol=h2, code=200, message=, url=https://1.1.1.1/dns-query?dns=AAABAAABAAAAAAAAA3d3dwhmYWNlYm9vawNjb20AAAEAAQ}
01-01 12:53:32.817 10999 11090 D OkHttp : [55 ms] responseBodyStart
01-01 12:53:32.818 10999 11090 D OkHttp : [56 ms] responseBodyEnd: byteCount=128
01-01 12:53:32.818 10999 11090 D OkHttp : [56 ms] connectionReleased
01-01 12:53:32.818 10999 11090 D OkHttp : [56 ms] callEnd
```
3. Run tests using gradle
```
$ ANDROID_SDK_ROOT=/Users/myusername/Library/Android/sdk ./gradlew :android-test:connectedCheck -PandroidBuild=true
...
> Task :android-test:connectedDebugAndroidTest
...
11:55:40 V/InstrumentationResultParser: Time: 13.271
11:55:40 V/InstrumentationResultParser:
11:55:40 V/InstrumentationResultParser: OK (12 tests)
...
11:55:40 I/XmlResultReporter: XML test result file generated at /Users/myusername/workspace/okhttp/android-test/build/outputs/androidTest-results/connected/TEST-pixel3a-Q(AVD) - 10-android-test-.xml. Total tests 13, passed 11, assumption_failure 1, ignored 1,
...
BUILD SUCCESSFUL in 1m 30s
63 actionable tasks: 61 executed, 2 up-to-date
```
n.b. use ANDROID_SERIAL=emulator-5554 or similar if you need to select between devices.
================================================
FILE: android-test/src/androidDeviceTest/java/okhttp/android/test/OkHttpTest.kt
================================================
/*
* Copyright (C) 2019 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp.android.test
import android.content.Context
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import androidx.test.platform.app.InstrumentationRegistry
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
import com.google.android.gms.security.ProviderInstaller
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import java.io.IOException
import java.net.InetAddress
import java.net.UnknownHostException
import java.security.KeyStore
import java.security.SecureRandom
import java.security.Security
import java.security.cert.Certificate
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import java.util.concurrent.atomic.AtomicInteger
import java.util.logging.Handler
import java.util.logging.Level
import java.util.logging.LogRecord
import java.util.logging.Logger
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLPeerUnverifiedException
import javax.net.ssl.SSLSocket
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
import mockwebserver3.MockResponse
import mockwebserver3.MockWebServer
import mockwebserver3.junit5.StartStop
import okhttp3.Cache
import okhttp3.Call
import okhttp3.CallEvent.CallEnd
import okhttp3.CallEvent.CallStart
import okhttp3.CallEvent.ConnectEnd
import okhttp3.CallEvent.ConnectStart
import okhttp3.CallEvent.ConnectionAcquired
import okhttp3.CallEvent.ConnectionReleased
import okhttp3.CallEvent.DnsEnd
import okhttp3.CallEvent.DnsStart
import okhttp3.CallEvent.FollowUpDecision
import okhttp3.CallEvent.ProxySelectEnd
import okhttp3.CallEvent.ProxySelectStart
import okhttp3.CallEvent.RequestHeadersEnd
import okhttp3.CallEvent.RequestHeadersStart
import okhttp3.CallEvent.ResponseBodyEnd
import okhttp3.CallEvent.ResponseBodyStart
import okhttp3.CallEvent.ResponseHeadersEnd
import okhttp3.CallEvent.ResponseHeadersStart
import okhttp3.CallEvent.SecureConnectEnd
import okhttp3.CallEvent.SecureConnectStart
import okhttp3.CertificatePinner
import okhttp3.CompressionInterceptor
import okhttp3.Connection
import okhttp3.DelegatingSSLSocket
import okhttp3.DelegatingSSLSocketFactory
import okhttp3.EventListener
import okhttp3.EventRecorder
import okhttp3.Gzip
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.OkHttpClientTestRule
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.TlsVersion
import okhttp3.brotli.Brotli
import okhttp3.dnsoverhttps.DnsOverHttps
import okhttp3.internal.concurrent.TaskRunner
import okhttp3.internal.http2.Http2
import okhttp3.internal.platform.Android10Platform
import okhttp3.internal.platform.AndroidPlatform
import okhttp3.internal.platform.Platform
import okhttp3.internal.platform.PlatformRegistry
import okhttp3.logging.LoggingEventListener
import okhttp3.testing.PlatformRule
import okhttp3.tls.HandshakeCertificates
import okhttp3.tls.internal.TlsUtil.localhost
import okhttp3.zstd.Zstd
import okio.ByteString.Companion.toByteString
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
import org.conscrypt.Conscrypt
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assertions.fail
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
import org.opentest4j.TestAbortedException
@Tag("Slow")
class OkHttpTest {
@Suppress("RedundantVisibilityModifier")
@JvmField
@RegisterExtension
public val platform = PlatformRule()
@Suppress("RedundantVisibilityModifier")
@JvmField
@RegisterExtension
public val clientTestRule =
OkHttpClientTestRule().apply {
logger = Logger.getLogger(OkHttpTest::class.java.name)
}
private var client: OkHttpClient =
clientTestRule
.newClientBuilder()
.addInterceptor(CompressionInterceptor(Zstd, Brotli, Gzip))
.build()
private val moshi =
Moshi
.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val handshakeCertificates = localhost()
@StartStop
private val server = MockWebServer()
@BeforeEach
fun setup() {
// Needed because of Platform.resetForTests
PlatformRegistry.applicationContext = ApplicationProvider.getApplicationContext()
}
@Test
fun testPlatform() {
assertTrue(Platform.isAndroid)
if (Build.VERSION.SDK_INT >= 29) {
assertTrue(Platform.get() is Android10Platform)
} else {
assertTrue(Platform.get() is AndroidPlatform)
}
}
@Test
fun testRequest() {
assumeNetwork()
val request = Request.Builder().url("https://api.twitter.com/robots.txt").build()
val response = client.newCall(request).execute()
response.use {
assertEquals(200, response.code)
}
}
@Test
fun testLocalhostInsecure() {
assumeTrue(Build.VERSION.SDK_INT >= 24)
val clientCertificates =
HandshakeCertificates
.Builder()
.apply {
if (Build.VERSION.SDK_INT >= 24) {
addInsecureHost(server.hostName)
}
}.build()
client =
client
.newBuilder()
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)
.build()
localhostInsecureRequest()
}
@Test
fun testRequestWithSniRequirement() {
assumeNetwork()
val request = Request.Builder().url("https://docs.fabric.io/android/changelog.html").build()
val response = client.newCall(request).execute()
response.use {
assertEquals(200, response.code)
}
}
@Test
fun testConscryptRequest() {
assumeNetwork()
try {
Security.insertProviderAt(Conscrypt.newProviderBuilder().build(), 1)
val request = Request.Builder().url("https://facebook.com/robots.txt").build()
var socketClass: String? = null
// Need fresh client to reset sslSocketFactoryOrNull
client =
OkHttpClient
.Builder()
.eventListenerFactory(
clientTestRule.wrap(
object : EventListener() {
override fun connectionAcquired(
call: Call,
connection: Connection,
) {
socketClass = connection.socket().javaClass.name
}
},
),
).build()
val response = client.newCall(request).execute()
response.use {
assertEquals(Protocol.HTTP_2, response.protocol)
assertEquals(200, response.code)
// see https://github.com/google/conscrypt/blob/b9463b2f74df42d85c73715a5f19e005dfb7b802/android/src/main/java/org/conscrypt/Platform.java#L613
when {
Build.VERSION.SDK_INT >= 24 -> {
// Conscrypt 2.5+ defaults to SSLEngine-based SSLSocket
assertEquals("org.conscrypt.Java8EngineSocket", socketClass)
}
Build.VERSION.SDK_INT < 22 -> {
assertEquals("org.conscrypt.KitKatPlatformOpenSSLSocketImplAdapter", socketClass)
}
else -> {
assertEquals("org.conscrypt.ConscryptFileDescriptorSocket", socketClass)
}
}
assertEquals(TlsVersion.TLS_1_3, response.handshake?.tlsVersion)
}
} finally {
Security.removeProvider("Conscrypt")
client.close()
}
}
@Test
fun testConscryptRequestLocalhostInsecure() {
try {
Security.insertProviderAt(Conscrypt.newProviderBuilder().build(), 1)
val clientCertificates =
HandshakeCertificates
.Builder()
.addInsecureHost(server.hostName)
.build()
// Need fresh client to reset sslSocketFactoryOrNull
client =
OkHttpClient
.Builder()
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)
.build()
localhostInsecureRequest()
} finally {
Security.removeProvider("Conscrypt")
client.close()
}
}
@Test
fun testRequestUsesPlayProvider() {
assumeNetwork()
try {
try {
ProviderInstaller.installIfNeeded(InstrumentationRegistry.getInstrumentation().targetContext)
} catch (gpsnae: GooglePlayServicesNotAvailableException) {
throw TestAbortedException("Google Play Services not available", gpsnae)
}
val request = Request.Builder().url("https://facebook.com/robots.txt").build()
var socketClass: String? = null
// Need fresh client to reset sslSocketFactoryOrNull
client =
OkHttpClient
.Builder()
.eventListenerFactory(
clientTestRule.wrap(
object : EventListener() {
override fun connectionAcquired(
call: Call,
connection: Connection,
) {
socketClass = connection.socket().javaClass.name
}
},
),
).build()
val response = client.newCall(request).execute()
response.use {
assertEquals(Protocol.HTTP_2, response.protocol)
assertEquals(200, response.code)
assertEquals("com.google.android.gms.org.conscrypt.Java8FileDescriptorSocket", socketClass)
assertEquals(TlsVersion.TLS_1_2, response.handshake?.tlsVersion)
}
} finally {
Security.removeProvider("GmsCore_OpenSSL")
client.close()
}
}
@Test
fun testRequestUsesPlayProviderLocalhostInsecure() {
try {
try {
ProviderInstaller.installIfNeeded(InstrumentationRegistry.getInstrumentation().targetContext)
} catch (gpsnae: GooglePlayServicesNotAvailableException) {
throw TestAbortedException("Google Play Services not available", gpsnae)
}
val clientCertificates =
HandshakeCertificates
.Builder()
.addPlatformTrustedCertificates()
.addInsecureHost(server.hostName)
.build()
// Need fresh client to reset sslSocketFactoryOrNull
client =
OkHttpClient
.Builder()
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)
.build()
localhostInsecureRequest()
} finally {
Security.removeProvider("GmsCore_OpenSSL")
client.close()
}
}
private fun localhostInsecureRequest() {
server.useHttps(handshakeCertificates.sslSocketFactory())
server.enqueue(MockResponse())
val request = Request.Builder().url(server.url("/")).build()
client.newCall(request).execute().use {
assertEquals(200, it.code)
assertEquals(listOf(), it.handshake?.peerCertificates)
}
}
@Test
fun testRequestUsesAndroidConscrypt() {
assumeNetwork()
val request = Request.Builder().url("https://facebook.com/robots.txt").build()
var socketClass: String? = null
client =
client
.newBuilder()
.eventListenerFactory(
clientTestRule.wrap(
object : EventListener() {
override fun connectionAcquired(
call: Call,
connection: Connection,
) {
socketClass = connection.socket().javaClass.name
}
},
),
).build()
val response = client.newCall(request).execute()
response.use {
assertEquals(Protocol.HTTP_2, response.protocol)
if (Build.VERSION.SDK_INT >= 29) {
assertEquals(TlsVersion.TLS_1_3, response.handshake?.tlsVersion)
} else {
assertEquals(TlsVersion.TLS_1_2, response.handshake?.tlsVersion)
}
assertEquals(200, response.code)
assertTrue(socketClass?.startsWith("com.android.org.conscrypt.") == true)
}
}
@Test
fun testRequestUsesAndroidConscryptLocalhostInsecure() {
assumeTrue(Build.VERSION.SDK_INT >= 24)
val clientCertificates =
HandshakeCertificates
.Builder()
.addPlatformTrustedCertificates()
.apply {
if (Build.VERSION.SDK_INT >= 24) {
addInsecureHost(server.hostName)
}
}.build()
client =
client
.newBuilder()
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)
.build()
localhostInsecureRequest()
}
@Test
fun testHttpRequestNotBlockedOnLegacyAndroid() {
assumeTrue(Build.VERSION.SDK_INT < 23)
val request = Request.Builder().url("http://squareup.com/robots.txt").build()
val response = client.newCall(request).execute()
response.use {
assertEquals(200, response.code)
}
}
@Test
@Disabled("cleartext required for additional okhttp wide tests")
fun testHttpRequestBlocked() {
assumeTrue(Build.VERSION.SDK_INT >= 23)
val request = Request.Builder().url("http://squareup.com/robots.txt").build()
try {
client.newCall(request).execute()
fail("expected cleartext blocking")
} catch (_: java.net.UnknownServiceException) {
}
}
data class HowsMySslResults(
val unknown_cipher_suite_supported: Boolean,
val beast_vuln: Boolean,
val session_ticket_supported: Boolean,
val tls_compression_supported: Boolean,
val ephemeral_keys_supported: Boolean,
val rating: String,
val tls_version: String,
val able_to_detect_n_minus_one_splitting: Boolean,
val insecure_cipher_suites: Map>,
val given_cipher_suites: List?,
)
@Test
@Disabled
fun testSSLFeatures() {
assumeNetwork()
val request = Request.Builder().url("https://www.howsmyssl.com/a/check").build()
val response = client.newCall(request).execute()
val results =
response.use {
moshi.adapter(HowsMySslResults::class.java).fromJson(response.body.string())!!
}
Platform.get().log("results $results", Platform.WARN)
assertTrue(results.session_ticket_supported)
assertEquals("Probably Okay", results.rating)
// TODO map to expected versions automatically, test ignored for now. Run manually.
assertEquals("TLS 1.3", results.tls_version)
assertEquals(0, results.insecure_cipher_suites.size)
assertEquals(TlsVersion.TLS_1_3, response.handshake?.tlsVersion)
assertEquals(Protocol.HTTP_2, response.protocol)
}
@Test
fun testMockWebserverRequest() {
enableTls()
server.enqueue(MockResponse(body = "abc"))
val request = Request.Builder().url(server.url("/")).build()
val response = client.newCall(request).execute()
response.use {
assertEquals(200, response.code)
assertEquals(Protocol.HTTP_2, response.protocol)
val tlsVersion = response.handshake?.tlsVersion
assertTrue(tlsVersion == TlsVersion.TLS_1_2 || tlsVersion == TlsVersion.TLS_1_3)
assertEquals(
"CN=localhost",
(response.handshake!!.peerCertificates.first() as X509Certificate).subjectDN.name,
)
}
}
@Test
fun testCertificatePinningFailure() {
enableTls()
val certificatePinner =
CertificatePinner
.Builder()
.add(server.hostName, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build()
client = client.newBuilder().certificatePinner(certificatePinner).build()
server.enqueue(MockResponse(body = "abc"))
val request = Request.Builder().url(server.url("/")).build()
try {
client.newCall(request).execute()
fail("")
} catch (_: SSLPeerUnverifiedException) {
}
}
@Test
fun testCertificatePinningSuccess() {
enableTls()
val certificatePinner =
CertificatePinner
.Builder()
.add(
server.hostName,
CertificatePinner.pin(handshakeCertificates.trustManager.acceptedIssuers[0]),
).build()
client = client.newBuilder().certificatePinner(certificatePinner).build()
server.enqueue(MockResponse(body = "abc"))
val request = Request.Builder().url(server.url("/")).build()
val response = client.newCall(request).execute()
response.use {
assertEquals(200, response.code)
}
}
@Test
fun testEventListener() {
val eventRecorder = EventRecorder()
enableTls()
client =
client
.newBuilder()
.eventListenerFactory(clientTestRule.wrap(eventRecorder))
.build()
server.enqueue(MockResponse(body = "abc1"))
server.enqueue(MockResponse(body = "abc2"))
val request = Request.Builder().url(server.url("/")).build()
client.newCall(request).execute().use { response ->
assertEquals(200, response.code)
}
assertEquals(
listOf(
CallStart::class,
ProxySelectStart::class,
ProxySelectEnd::class,
DnsStart::class,
DnsEnd::class,
ConnectStart::class,
SecureConnectStart::class,
SecureConnectEnd::class,
ConnectEnd::class,
ConnectionAcquired::class,
RequestHeadersStart::class,
RequestHeadersEnd::class,
ResponseHeadersStart::class,
ResponseHeadersEnd::class,
FollowUpDecision::class,
ResponseBodyStart::class,
ResponseBodyEnd::class,
ConnectionReleased::class,
CallEnd::class,
),
eventRecorder.recordedEventTypes(),
)
eventRecorder.clearAllEvents()
client.newCall(request).execute().use { response ->
assertEquals(200, response.code)
}
assertEquals(
listOf(
CallStart::class,
ConnectionAcquired::class,
RequestHeadersStart::class,
RequestHeadersEnd::class,
ResponseHeadersStart::class,
ResponseHeadersEnd::class,
FollowUpDecision::class,
ResponseBodyStart::class,
ResponseBodyEnd::class,
ConnectionReleased::class,
CallEnd::class,
),
eventRecorder.recordedEventTypes(),
)
}
@Test
fun testSessionReuse() {
val sessionIds = mutableListOf()
enableTls()
client =
client
.newBuilder()
.eventListenerFactory(
clientTestRule.wrap(
object : EventListener() {
override fun connectionAcquired(
call: Call,
connection: Connection,
) {
val sslSocket = connection.socket() as SSLSocket
sessionIds.add(
sslSocket.session.id
.toByteString()
.hex(),
)
}
},
),
).build()
server.enqueue(MockResponse(body = "abc1"))
server.enqueue(MockResponse(body = "abc2"))
val request = Request.Builder().url(server.url("/")).build()
client.newCall(request).execute().use { response ->
assertEquals(200, response.code)
}
client.connectionPool.evictAll()
assertEquals(0, client.connectionPool.connectionCount())
client.newCall(request).execute().use { response ->
assertEquals(200, response.code)
}
assertEquals(2, sessionIds.size)
assertEquals(sessionIds[0], sessionIds[1])
}
@Test
fun testDnsOverHttps() {
assumeNetwork()
client =
client
.newBuilder()
.eventListenerFactory(clientTestRule.wrap(LoggingEventListener.Factory()))
.build()
val dohDns = buildCloudflareIp(client)
val dohEnabledClient =
client
.newBuilder()
.eventListener(EventListener.NONE)
.dns(dohDns)
.build()
dohEnabledClient.get("https://www.twitter.com/robots.txt")
dohEnabledClient.get("https://www.facebook.com/robots.txt")
}
@Test
fun testCustomTrustManager() {
assumeNetwork()
val trustManager =
object : X509TrustManager {
override fun checkClientTrusted(
chain: Array?,
authType: String?,
) {}
override fun checkServerTrusted(
chain: Array?,
authType: String?,
) {}
override fun getAcceptedIssuers(): Array = arrayOf()
}
val sslContext =
Platform.get().newSSLContext().apply {
init(null, arrayOf(trustManager), null)
}
val sslSocketFactory = sslContext.socketFactory
val hostnameVerifier = HostnameVerifier { _, _ -> true }
client =
client
.newBuilder()
.sslSocketFactory(sslSocketFactory, trustManager)
.hostnameVerifier(hostnameVerifier)
.build()
client.get("https://www.facebook.com/robots.txt")
}
@Test
fun testCustomSSLSocketFactoryWithoutALPN() {
enableTls()
server.enqueue(MockResponse(body = "abc"))
val sslSocketFactory = client.sslSocketFactory
val trustManager = client.x509TrustManager!!
val delegatingSocketFactory =
object : DelegatingSSLSocketFactory(sslSocketFactory) {
override fun configureSocket(sslSocket: SSLSocket): SSLSocket =
object : DelegatingSSLSocket(sslSocket) {
override fun getApplicationProtocol(): String = throw UnsupportedOperationException()
}
}
client =
client
.newBuilder()
.sslSocketFactory(delegatingSocketFactory, trustManager)
.build()
val request = Request.Builder().url(server.url("/").toString()).build()
val response = client.newCall(request).execute()
response.use {
assertEquals(200, response.code)
assertEquals(Protocol.HTTP_1_1, response.protocol)
}
}
@Test
fun testCustomTrustManagerWithAndroidCheck() {
assumeNetwork()
var withHostCalled = false
var withoutHostCalled = false
val trustManager =
object : X509TrustManager {
override fun checkClientTrusted(
chain: Array?,
authType: String?,
) {}
override fun checkServerTrusted(
chain: Array?,
authType: String?,
) {
withoutHostCalled = true
}
// called by Android via reflection in X509TrustManagerExtensions
@Suppress("unused", "UNUSED_PARAMETER")
fun checkServerTrusted(
chain: Array,
authType: String,
hostname: String,
): List {
withHostCalled = true
return chain.toList()
}
override fun getAcceptedIssuers(): Array = arrayOf()
}
val sslContext =
Platform.get().newSSLContext().apply {
init(null, arrayOf(trustManager), null)
}
val sslSocketFactory = sslContext.socketFactory
val hostnameVerifier = HostnameVerifier { _, _ -> true }
client =
client
.newBuilder()
.sslSocketFactory(sslSocketFactory, trustManager)
.hostnameVerifier(hostnameVerifier)
.build()
client.get("https://www.facebook.com/robots.txt")
if (Build.VERSION.SDK_INT < 24) {
assertFalse(withHostCalled)
assertTrue(withoutHostCalled)
} else {
assertTrue(withHostCalled)
assertFalse(withoutHostCalled)
}
}
@Test
fun testUnderscoreRequest() {
assumeNetwork()
val request =
Request.Builder().url("https://example_underscore_123.s3.amazonaws.com/").build()
try {
client.newCall(request).execute().close()
// Hopefully this passes
} catch (ioe: IOException) {
// https://github.com/square/okhttp/issues/5840
when (ioe.cause) {
is IllegalArgumentException -> {
assertEquals("Android internal error", ioe.message)
}
is CertificateException -> {
assertTrue(ioe.cause?.cause is IllegalArgumentException)
assertEquals(
true,
ioe.cause
?.cause
?.message
?.startsWith("Invalid input to toASCII"),
)
}
else -> {
throw ioe
}
}
}
}
@Test
@Disabled("breaks conscrypt test")
fun testBouncyCastleRequest() {
assumeNetwork()
try {
Security.insertProviderAt(BouncyCastleProvider(), 1)
Security.insertProviderAt(BouncyCastleJsseProvider(), 2)
var socketClass: String? = null
val trustManager =
TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm())
.apply {
init(null as KeyStore?)
}.trustManagers
.first() as X509TrustManager
val sslContext =
Platform.get().newSSLContext().apply {
// TODO remove most of this code after https://github.com/bcgit/bc-java/issues/686
init(null, arrayOf(trustManager), SecureRandom())
}
client =
client
.newBuilder()
.sslSocketFactory(sslContext.socketFactory, trustManager)
.eventListenerFactory(
clientTestRule.wrap(
object : EventListener() {
override fun connectionAcquired(
call: Call,
connection: Connection,
) {
socketClass = connection.socket().javaClass.name
}
},
),
).build()
val request = Request.Builder().url("https://facebook.com/robots.txt").build()
val response = client.newCall(request).execute()
response.use {
assertEquals(Protocol.HTTP_2, response.protocol)
assertEquals(200, response.code)
assertEquals("org.bouncycastle.jsse.provider.ProvSSLSocketWrap", socketClass)
assertEquals(TlsVersion.TLS_1_2, response.handshake?.tlsVersion)
}
} finally {
Security.removeProvider("BCJSSE")
Security.removeProvider("BC")
}
}
@Test
@Disabled("TODO: currently logging okhttp3.internal.concurrent.TaskRunner, okhttp3.internal.http2.Http2")
fun testLoggingLevels() {
enableTls()
val testHandler =
object : Handler() {
val calls = mutableMapOf()
override fun publish(record: LogRecord) {
calls
.getOrPut(record.loggerName) { AtomicInteger(0) }
.incrementAndGet()
}
override fun flush() {
}
override fun close() {
}
}.apply {
level = Level.FINEST
}
Logger
.getLogger("")
.addHandler(testHandler)
Logger
.getLogger("okhttp3")
.addHandler(testHandler)
Logger
.getLogger(Http2::class.java.name)
.addHandler(testHandler)
Logger
.getLogger(TaskRunner::class.java.name)
.addHandler(testHandler)
Logger
.getLogger(OkHttpClient::class.java.name)
.addHandler(testHandler)
server.enqueue(MockResponse(body = "abc"))
val request =
Request
.Builder()
.url(server.url("/"))
.build()
val response =
client
.newCall(request)
.execute()
response.use {
assertEquals(200, response.code)
assertEquals(Protocol.HTTP_2, response.protocol)
}
// Only logs to the test logger above
// Will fail if "adb shell setprop log.tag.okhttp.Http2 DEBUG" is called
assertEquals(setOf(OkHttpTest::class.java.name), testHandler.calls.keys)
}
fun testCachedRequest() {
enableTls()
server.enqueue(MockResponse(body = "abc", headers = Headers.headersOf("cache-control", "public, max-age=3")))
server.enqueue(MockResponse(body = "abc"))
val ctxt = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
val cacheSize = 1L * 1024 * 1024 // 1MB
val cache = Cache(ctxt.cacheDir.resolve("testCache"), cacheSize)
try {
client =
client
.newBuilder()
.cache(cache)
.build()
val request =
Request
.Builder()
.url(server.url("/"))
.build()
client
.newCall(request)
.execute()
.use {
assertEquals(200, it.code)
assertNull(it.cacheResponse)
assertNotNull(it.networkResponse)
assertEquals(3, it.cacheControl.maxAgeSeconds)
assertTrue(it.cacheControl.isPublic)
}
client
.newCall(request)
.execute()
.use {
assertEquals(200, it.code)
assertNotNull(it.cacheResponse)
assertNull(it.networkResponse)
}
assertEquals(1, cache.hitCount())
assertEquals(1, cache.networkCount())
assertEquals(2, cache.requestCount())
} finally {
cache.delete()
}
}
private fun OkHttpClient.get(url: String) {
val request = Request.Builder().url(url).build()
val response = this.newCall(request).execute()
response.use {
assertEquals(200, response.code)
}
}
fun buildCloudflareIp(bootstrapClient: OkHttpClient): DnsOverHttps =
DnsOverHttps
.Builder()
.client(bootstrapClient)
.url("https://1.1.1.1/dns-query".toHttpUrl())
.build()
private fun enableTls() {
client =
client
.newBuilder()
.sslSocketFactory(
handshakeCertificates.sslSocketFactory(),
handshakeCertificates.trustManager,
).build()
server.useHttps(handshakeCertificates.sslSocketFactory())
}
private fun assumeNetwork() {
try {
InetAddress.getByName("www.google.com")
} catch (uhe: UnknownHostException) {
throw TestAbortedException(uhe.message, uhe)
}
}
fun OkHttpClient.close() {
dispatcher.executorService.shutdown()
connectionPool.evictAll()
}
}
================================================
FILE: android-test/src/androidDeviceTest/java/okhttp/android/test/SingleAndroidTest.kt
================================================
/*
* Copyright (C) 2025 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp.android.test
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import mockwebserver3.MockResponse
import mockwebserver3.MockWebServer
import okhttp3.ConnectionPool
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.tls.internal.TlsUtil.localhost
import org.junit.jupiter.api.Test
/**
* This single Junit 4 test is our Android test suite on API 21-25.
*/
class SingleAndroidTest {
private val handshakeCertificates = localhost()
private var client: OkHttpClient =
OkHttpClient
.Builder()
.sslSocketFactory(
handshakeCertificates.sslSocketFactory(),
handshakeCertificates.trustManager,
).connectionPool(ConnectionPool(0, 1, TimeUnit.SECONDS))
.build()
private val server =
MockWebServer()
@Test
fun testHttpsRequest() {
server.useHttps(handshakeCertificates.sslSocketFactory())
server.enqueue(MockResponse())
server.start()
val request = Request.Builder().url(server.url("/")).build()
val response = client.newCall(request).execute()
response.use {
assertEquals(200, response.code)
}
while (client.connectionPool.connectionCount() > 0) {
Thread.sleep(1000)
}
}
@Test
fun testHttpRequest() {
server.enqueue(MockResponse())
server.start()
val request = Request.Builder().url(server.url("/")).build()
val response = client.newCall(request).execute()
response.use {
assertEquals(200, response.code)
}
while (client.connectionPool.connectionCount() > 0) {
Thread.sleep(1000)
}
}
}
================================================
FILE: android-test/src/androidDeviceTest/java/okhttp/android/test/StrictModeTest.kt
================================================
/*
* Copyright (C) 2025 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp.android.test
import android.os.StrictMode
import android.os.StrictMode.ThreadPolicy
import android.os.strictmode.Violation
import androidx.test.filters.SdkSuppress
import assertk.assertThat
import assertk.assertions.hasSize
import assertk.assertions.isEmpty
import assertk.assertions.isEqualTo
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.internal.platform.Platform
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.parallel.Isolated
@Isolated
@SdkSuppress(minSdkVersion = 28)
class StrictModeTest {
private val violations = mutableListOf()
@AfterEach
fun cleanup() {
StrictMode.setThreadPolicy(
ThreadPolicy
.Builder()
.permitAll()
.build(),
)
}
@Test
fun testInit() {
Platform.resetForTests()
applyStrictMode()
// Not currently safe
// See https://github.com/square/okhttp/pull/8248
OkHttpClient()
assertThat(violations).hasSize(1)
assertThat(violations[0].message).isEqualTo("newSSLContext")
}
@Test
fun testNewCall() {
Platform.resetForTests()
val client = OkHttpClient()
applyStrictMode()
// Safe on main
client.newCall(Request("https://google.com/robots.txt".toHttpUrl()))
assertThat(violations).isEmpty()
}
private fun applyStrictMode() {
StrictMode.setThreadPolicy(
ThreadPolicy
.Builder()
.detectCustomSlowCalls()
.penaltyListener({ it.run() }) {
violations.add(it)
}.build(),
)
}
}
================================================
FILE: android-test/src/androidDeviceTest/java/okhttp/android/test/alpn/AlpnOverrideTest.kt
================================================
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp.android.test.alpn
import android.os.Build
import android.util.Log
import assertk.assertThat
import assertk.assertions.isEqualTo
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
import okhttp3.Call
import okhttp3.Connection
import okhttp3.ConnectionSpec
import okhttp3.DelegatingSSLSocketFactory
import okhttp3.EventListener
import okhttp3.OkHttpClient
import okhttp3.Request
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
/**
* Tests for ALPN overriding on Android.
*/
@Tag("Remote")
class AlpnOverrideTest {
class CustomSSLSocketFactory(
delegate: SSLSocketFactory,
) : DelegatingSSLSocketFactory(delegate) {
override fun configureSocket(sslSocket: SSLSocket): SSLSocket {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val parameters = sslSocket.sslParameters
Log.d("CustomSSLSocketFactory", "old applicationProtocols: $parameters.applicationProtocols")
parameters.applicationProtocols = arrayOf("x-amzn-http-ca")
sslSocket.sslParameters = parameters
}
return sslSocket
}
}
var client = OkHttpClient()
@Test
fun getWithCustomSocketFactory() {
client =
client
.newBuilder()
.sslSocketFactory(CustomSSLSocketFactory(client.sslSocketFactory), client.x509TrustManager!!)
.connectionSpecs(
listOf(
ConnectionSpec
.Builder(ConnectionSpec.MODERN_TLS)
.supportsTlsExtensions(false)
.build(),
),
).eventListener(
object : EventListener() {
override fun connectionAcquired(
call: Call,
connection: Connection,
) {
val sslSocket = connection.socket() as SSLSocket
println("Requested " + sslSocket.sslParameters.applicationProtocols.joinToString())
println("Negotiated " + sslSocket.applicationProtocol)
}
},
).build()
val request =
Request
.Builder()
.url("https://www.google.com")
.build()
client.newCall(request).execute().use { response ->
assertThat(response.code).isEqualTo(200)
}
}
}
================================================
FILE: android-test/src/androidDeviceTest/java/okhttp/android/test/letsencrypt/LetsEncryptClientTest.kt
================================================
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp.android.test.letsencrypt
import android.os.Build
import assertk.assertThat
import assertk.assertions.isEqualTo
import java.security.cert.X509Certificate
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.tls.HandshakeCertificates
import okhttp3.tls.decodeCertificatePem
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
/**
* Test for new Let's Encrypt Root Certificate.
*/
@Tag("Remote")
class LetsEncryptClientTest {
@Test fun get() {
// These tests wont actually run before Android 8.0 as per
// https://github.com/mannodermaus/android-junit5
// Raised https://github.com/mannodermaus/android-junit5/issues/228 to reevaluate
val androidMorEarlier = Build.VERSION.SDK_INT <= 23
val clientBuilder = OkHttpClient.Builder()
if (androidMorEarlier) {
val cert: X509Certificate =
"""
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
""".trimIndent().decodeCertificatePem()
val handshakeCertificates =
HandshakeCertificates
.Builder()
// TODO reenable in official answers
// .addPlatformTrustedCertificates()
.addTrustedCertificate(cert)
.build()
clientBuilder
.sslSocketFactory(
handshakeCertificates.sslSocketFactory(),
handshakeCertificates.trustManager,
)
}
val client = clientBuilder.build()
val request =
Request
.Builder()
.url("https://valid-isrgrootx1.letsencrypt.org/robots.txt")
.build()
client.newCall(request).execute().use { response ->
assertThat(response.code).isEqualTo(404)
assertThat(response.protocol).isEqualTo(Protocol.HTTP_2)
}
}
}
================================================
FILE: android-test/src/androidDeviceTest/java/okhttp/android/test/sni/SniOverrideTest.kt
================================================
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp.android.test.sni
import android.os.Build
import android.util.Log
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.isEqualTo
import java.security.cert.X509Certificate
import javax.net.ssl.SNIHostName
import javax.net.ssl.SNIServerName
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
import okhttp3.DelegatingSSLSocketFactory
import okhttp3.Dns
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
/**
* Tests for SNI overriding on Android.
*/
@Tag("Remote")
class SniOverrideTest {
var client =
OkHttpClient
.Builder()
.build()
@Test
fun getWithCustomSocketFactory() {
assumeTrue(Build.VERSION.SDK_INT >= 24)
class CustomSSLSocketFactory(
delegate: SSLSocketFactory,
) : DelegatingSSLSocketFactory(delegate) {
override fun configureSocket(sslSocket: SSLSocket): SSLSocket {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val parameters = sslSocket.sslParameters
val sni = parameters.serverNames
Log.d("CustomSSLSocketFactory", "old SNI: $sni")
parameters.serverNames = mutableListOf(SNIHostName("cloudflare-dns.com"))
sslSocket.sslParameters = parameters
}
return sslSocket
}
}
client =
client
.newBuilder()
.sslSocketFactory(CustomSSLSocketFactory(client.sslSocketFactory), client.x509TrustManager!!)
.hostnameVerifier { hostname, session ->
val s = "hostname: $hostname peerHost:${session.peerHost}"
Log.d("SniOverrideTest", s)
try {
val cert = session.peerCertificates[0] as X509Certificate
for (name in cert.subjectAlternativeNames) {
if (name[0] as Int == 2) {
Log.d("SniOverrideTest", "cert: " + name[1])
}
}
true
} catch (e: Exception) {
false
}
}.build()
val request =
Request
.Builder()
.url("https://sni.cloudflaressl.com/cdn-cgi/trace")
.header("Host", "cloudflare-dns.com")
.build()
client.newCall(request).execute().use { response ->
assertThat(response.code).isEqualTo(200)
assertThat(response.protocol).isEqualTo(Protocol.HTTP_2)
assertThat(response.body.string()).contains("h=cloudflare-dns.com")
}
}
@Test
fun getWithDns() {
client =
client
.newBuilder()
.dns {
Dns.SYSTEM.lookup("sni.cloudflaressl.com")
}.build()
val request =
Request
.Builder()
.url("https://cloudflare-dns.com/cdn-cgi/trace")
.build()
client.newCall(request).execute().use { response ->
assertThat(response.code).isEqualTo(200)
assertThat(response.protocol).isEqualTo(Protocol.HTTP_2)
assertThat(response.body.string()).contains("h=cloudflare-dns.com")
}
}
}
================================================
FILE: android-test/src/main/AndroidManifest.xml
================================================
================================================
FILE: android-test/src/main/res/values/strings.xml
================================================
android-test
================================================
FILE: android-test/src/main/res/xml/network_security_config.xml
================================================
================================================
FILE: android-test/src/test/kotlin/okhttp/android/test/AndroidLoggingTest.kt
================================================
/*
* Copyright (c) 2022 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package okhttp.android.test
import android.util.Log
import assertk.assertThat
import assertk.assertions.containsExactly
import assertk.assertions.containsOnly
import assertk.assertions.isNull
import java.net.UnknownHostException
import okhttp3.ConnectionSpec
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.internal.platform.AndroidPlatform
import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.logging.LoggingEventListener
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.shadows.ShadowLog
@RunWith(RobolectricTestRunner::class)
class AndroidLoggingTest {
val clientBuilder =
OkHttpClient.Builder().connectionSpecs(listOf(ConnectionSpec.CLEARTEXT)).dns {
throw UnknownHostException("shortcircuit")
}
val request = Request("http://google.com/robots.txt".toHttpUrl())
@Test
fun testHttpLoggingInterceptor() {
val interceptor =
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BASIC
}
val client = clientBuilder.addInterceptor(interceptor).build()
try {
client.newCall(request).execute()
} catch (uhe: UnknownHostException) {
// expected
}
val logs = ShadowLog.getLogsForTag(AndroidPlatform.Tag)
assertThat(logs.map { it.type }).containsOnly(Log.INFO)
assertThat(
logs.map {
it.msg.replace(
"\\d+".toRegex(),
"",
)
},
).containsExactly(
"--> GET http://google.com/robots.txt",
"<-- HTTP FAILED: java.net.UnknownHostException: shortcircuit. ${request.url} (ms)",
)
// We should consider if these logs should retain Exceptions
assertThat(logs.last().throwable).isNull()
}
@Test
fun testLoggingEventListener() {
val client = clientBuilder.eventListenerFactory(LoggingEventListener.Factory()).build()
try {
client.newCall(request).execute()
} catch (uhe: UnknownHostException) {
// expected
}
val logs = ShadowLog.getLogsForTag(AndroidPlatform.Tag)
assertThat(logs.map { it.type }).containsOnly(Log.INFO)
assertThat(
logs.map {
it.msg.replace(
"\\[\\d+ ms] ".toRegex(),
"",
)
},
).containsExactly(
"callStart: Request{method=GET, url=http://google.com/robots.txt}",
"proxySelectStart: http://google.com/",
"proxySelectEnd: [DIRECT]",
"dnsStart: google.com",
"callFailed: java.net.UnknownHostException: shortcircuit",
)
// We should consider if these logs should retain Exceptions
assertThat(logs.last().throwable).isNull()
}
}
================================================
FILE: android-test/src/test/kotlin/okhttp/android/test/AndroidSocketAdapterTest.kt
================================================
/*
* Copyright (C) 2019 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp.android.test
import java.security.Provider
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocket
import okhttp3.DelegatingSSLSocket
import okhttp3.DelegatingSSLSocketFactory
import okhttp3.Protocol.HTTP_1_1
import okhttp3.Protocol.HTTP_2
import okhttp3.internal.platform.android.AndroidSocketAdapter
import okhttp3.internal.platform.android.ConscryptSocketAdapter
import okhttp3.internal.platform.android.DeferredSocketAdapter
import okhttp3.internal.platform.android.SocketAdapter
import okhttp3.internal.platform.android.StandardAndroidSocketAdapter
import org.conscrypt.Conscrypt
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters
@RunWith(ParameterizedRobolectricTestRunner::class)
class AndroidSocketAdapterTest(
val adapter: SocketAdapter,
) {
val context: SSLContext by lazy {
val provider: Provider = Conscrypt.newProviderBuilder().provideTrustManager(true).build()
SSLContext.getInstance("TLS", provider).apply {
init(null, null, null)
}
}
@Test
fun testMatchesSupportedSocket() {
val socketFactory = context.socketFactory
val sslSocket = socketFactory.createSocket() as SSLSocket
assertTrue(adapter.matchesSocket(sslSocket))
adapter.configureTlsExtensions(sslSocket, null, listOf(HTTP_2, HTTP_1_1))
// not connected
assertNull(adapter.getSelectedProtocol(sslSocket))
}
@Test
fun testMatchesSupportedAndroidSocketFactory() {
assumeTrue(adapter is StandardAndroidSocketAdapter)
assertTrue(adapter.matchesSocketFactory(context.socketFactory))
assertNotNull(adapter.trustManager(context.socketFactory))
}
@Test
fun testDoesntMatchSupportedCustomSocketFactory() {
assumeFalse(adapter is StandardAndroidSocketAdapter)
assertFalse(adapter.matchesSocketFactory(context.socketFactory))
assertNull(adapter.trustManager(context.socketFactory))
}
@Test
fun testCustomSocket() {
val socketFactory = DelegatingSSLSocketFactory(context.socketFactory)
assertFalse(adapter.matchesSocketFactory(socketFactory))
val sslSocket =
object : DelegatingSSLSocket(context.socketFactory.createSocket() as SSLSocket) {}
assertFalse(adapter.matchesSocket(sslSocket))
adapter.configureTlsExtensions(sslSocket, null, listOf(HTTP_2, HTTP_1_1))
// not connected
assertNull(adapter.getSelectedProtocol(sslSocket))
}
companion object {
@JvmStatic
@Parameters(name = "{0}")
fun data(): Collection =
listOfNotNull(
DeferredSocketAdapter(ConscryptSocketAdapter.factory),
DeferredSocketAdapter(AndroidSocketAdapter.factory("org.conscrypt")),
StandardAndroidSocketAdapter.buildIfSupported("org.conscrypt"),
)
}
}
================================================
FILE: android-test/src/test/kotlin/okhttp/android/test/BaseOkHttpClientUnitTest.kt
================================================
/*
* Copyright (c) 2025 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package okhttp.android.test
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isNotNull
import assertk.assertions.isNull
import java.net.InetAddress
import java.net.UnknownHostException
import okhttp3.Cache
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.Path.Companion.toPath
import okio.fakefilesystem.FakeFileSystem
import org.junit.AssumptionViolatedException
import org.junit.Before
import org.junit.Test
abstract class BaseOkHttpClientUnitTest {
private lateinit var client: OkHttpClient
@Before
fun setUp() {
client =
OkHttpClient
.Builder()
.cache(Cache(FakeFileSystem(), "/cache".toPath(), 10_000_000))
.build()
}
@Test
fun testRequestExternal() {
assumeNetwork()
val request = Request("https://www.google.com/robots.txt".toHttpUrl())
val networkRequest =
request
.newBuilder()
.build()
val call = client.newCall(networkRequest)
call.execute().use { response ->
assertThat(response.code).isEqualTo(200)
assertThat(response.cacheResponse).isNull()
}
val cachedCall = client.newCall(request)
cachedCall.execute().use { response ->
assertThat(response.code).isEqualTo(200)
assertThat(response.cacheResponse).isNotNull()
}
}
@Test
open fun testPublicSuffixDb() {
val httpUrl = "https://www.google.co.uk".toHttpUrl()
assertThat(httpUrl.topPrivateDomain()).isEqualTo("google.co.uk")
}
private fun assumeNetwork() {
try {
InetAddress.getByName("www.google.com")
} catch (uhe: UnknownHostException) {
throw AssumptionViolatedException(uhe.message, uhe)
}
}
}
================================================
FILE: android-test/src/test/kotlin/okhttp/android/test/DisabledInitialiserTest.kt
================================================
/*
* Copyright (c) 2025 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package okhttp.android.test
import assertk.all
import assertk.assertFailure
import assertk.assertions.cause
import assertk.assertions.hasClass
import assertk.assertions.hasMessage
import assertk.assertions.isNotNull
import java.io.IOException
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.internal.platform.Platform
import okhttp3.internal.platform.PlatformRegistry
import org.junit.AfterClass
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(
sdk = [23, 26, 30, 33, 35],
)
class DisabledInitialiserTest {
@Before
fun setContext() {
// Ensure we aren't succeeding because of another test
Platform.resetForTests()
PlatformRegistry.applicationContext = null
}
@Test
fun testWithoutContext() {
val httpUrl = "https://www.google.co.uk".toHttpUrl()
assertFailure { httpUrl.topPrivateDomain() }.all {
hasMessage("Unable to load PublicSuffixDatabase.list resource.")
cause().isNotNull().all {
hasMessage(
"Platform applicationContext not initialized. " +
"Startup Initializer possibly disabled, " +
"call OkHttp.initialize before test.",
)
hasClass()
}
}
}
companion object {
@AfterClass
@JvmStatic
fun resetContext() {
// Ensure we don't make other tests fail
Platform.resetForTests()
}
}
}
================================================
FILE: android-test/src/test/kotlin/okhttp/android/test/NonRobolectricOkHttpClientTest.kt
================================================
/*
* Copyright (c) 2025 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package okhttp.android.test
import assertk.all
import assertk.assertFailure
import assertk.assertions.cause
import assertk.assertions.hasClass
import assertk.assertions.hasMessage
import assertk.assertions.isNotNull
import java.io.IOException
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
/**
* Android test running with only stubs.
*/
@RunWith(JUnit4::class)
class NonRobolectricOkHttpClientTest : BaseOkHttpClientUnitTest() {
@Test
override fun testPublicSuffixDb() {
assertFailure { super.testPublicSuffixDb() }.all {
hasMessage("Unable to load PublicSuffixDatabase.list resource.")
cause().isNotNull().all {
hasMessage(
"Platform applicationContext not initialized. " +
"Possibly running Android unit test without Robolectric. " +
"Android tests should run with Robolectric " +
"and call OkHttp.initialize before test",
)
hasClass()
}
}
}
}
================================================
FILE: android-test/src/test/kotlin/okhttp/android/test/RobolectricOkHttpClientTest.kt
================================================
/*
* Copyright (c) 2025 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package okhttp.android.test
import androidx.test.core.app.ApplicationProvider
import okhttp3.OkHttp
import org.junit.Before
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(
sdk = [23, 26, 30, 33, 35],
)
class RobolectricOkHttpClientTest : BaseOkHttpClientUnitTest() {
@Before
fun setContext() {
// This is awkward because Robolectric won't run our initializers and we don't want test deps
// https://github.com/robolectric/robolectric/issues/8461
OkHttp.initialize(ApplicationProvider.getApplicationContext())
}
}
================================================
FILE: android-test/src/test/kotlin/okhttp/android/test/ShadowDnsResolver.kt
================================================
/*
* Copyright (C) 2024 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp.android.test
import android.net.DnsResolver
import android.net.Network
import android.os.CancellationSignal
import java.net.InetAddress
import java.util.concurrent.Executor
import org.robolectric.annotation.Implementation
import org.robolectric.annotation.Implements
import org.robolectric.shadow.api.Shadow
@Implements(DnsResolver::class)
class ShadowDnsResolver {
var responder: (Request) -> Unit = {
it.callback.onAnswer(listOf(), 0)
}
data class Request(
val network: Network?,
val domain: String,
val nsType: Int,
val flags: Int,
val callback: DnsResolver.Callback>,
)
@Implementation
fun query(
network: Network?,
domain: String,
nsType: Int,
flags: Int,
executor: Executor,
cancellationSignal: CancellationSignal?,
callback: DnsResolver.Callback>,
) {
responder(Request(network, domain, nsType, flags, callback))
}
companion object {
@Implementation
@JvmStatic
fun getInstance(): DnsResolver = Shadow.newInstance(DnsResolver::class.java, arrayOf(), arrayOf())
}
}
================================================
FILE: android-test-app/build.gradle.kts
================================================
@file:Suppress("UnstableApiUsage")
import okhttp3.buildsupport.testJavaVersion
plugins {
id("okhttp.base-conventions")
id("com.android.application")
}
android {
compileSdk = 36
namespace = "okhttp.android.testapp"
// Release APKs can't be tested currently with AGP
testBuildType = "debug"
defaultConfig {
minSdk = 21
targetSdk = 36
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
targetCompatibility(JavaVersion.VERSION_11)
sourceCompatibility(JavaVersion.VERSION_11)
}
buildTypes {
release {
isShrinkResources = true
isMinifyEnabled = true
signingConfig = signingConfigs.getByName("debug")
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
testProguardFiles("test-proguard-rules.pro")
}
}
lint {
abortOnError = true
}
}
dependencies {
implementation(libs.playservices.safetynet)
implementation(projects.okhttp)
implementation(libs.androidx.activity)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.assertk)
}
================================================
FILE: android-test-app/proguard-rules.pro
================================================
# no rules should be needed
-whyareyoukeeping class okhttp3.internal.idn.IdnaMappingTable {
*;
}
================================================
FILE: android-test-app/src/androidTest/kotlin/okhttp/android/testapp/IdnaTest.kt
================================================
/*
* Copyright (c) 2025 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.android
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.Assert.assertEquals
import org.junit.Test
class IdnaTest {
@Test
fun testHostnameFunction() {
assertEquals("xn--n3h.net", "https://☃.net/robots.txt".toHttpUrl().host)
}
}
================================================
FILE: android-test-app/src/androidTest/kotlin/okhttp/android/testapp/PublicSuffixDatabaseTest.kt
================================================
/*
* Copyright (C) 2023 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.android
import assertk.assertThat
import assertk.assertions.isEqualTo
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.junit.Test
/**
* Run with "./gradlew :android-test-app:connectedCheck -PandroidBuild=true" and make sure ANDROID_SDK_ROOT is set.
*/
class PublicSuffixDatabaseTest {
@Test
fun testTopLevelDomain() {
assertThat("https://www.google.com/robots.txt".toHttpUrl().topPrivateDomain()).isEqualTo("google.com")
}
}
================================================
FILE: android-test-app/src/main/AndroidManifest.xml
================================================
================================================
FILE: android-test-app/src/main/kotlin/okhttp/android/testapp/MainActivity.kt
================================================
/*
* Copyright (C) 2023 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp.android.testapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import okhttp3.Call
import okhttp3.Callback
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.internal.platform.AndroidPlatform
import okio.IOException
open class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val client = OkHttpClient()
// Ensure we are compiling against the right variant
println(AndroidPlatform.isSupported)
val url = "https://github.com/square/okhttp".toHttpUrl()
println(url.topPrivateDomain())
client.newCall(Request(url)).enqueue(
object : Callback {
override fun onFailure(
call: Call,
e: IOException,
) {
println("failed: $e")
}
override fun onResponse(
call: Call,
response: Response,
) {
println("response: ${response.code}")
response.close()
}
},
)
}
}
================================================
FILE: android-test-app/src/main/kotlin/okhttp/android/testapp/MainActivity2.kt
================================================
/*
* Copyright (C) 2025 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp.android.testapp
class MainActivity2 : MainActivity()
================================================
FILE: android-test-app/src/main/kotlin/okhttp/android/testapp/TestApplication.kt
================================================
/*
* Copyright (C) 2023 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp.android.testapp
import android.annotation.SuppressLint
import android.app.Application
import android.os.Build
import okhttp3.OkHttp
class TestApplication : Application() {
override fun onCreate() {
super.onCreate()
if (isSecondaryProcess()) {
OkHttp.initialize(applicationContext)
}
}
private fun isSecondaryProcess(): Boolean = getProcess() != packageName
@SuppressLint("DiscouragedPrivateApi")
private fun getProcess(): String? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
getProcessName()
} else {
Class
.forName("android.app.ActivityThread")
.getDeclaredMethod("currentProcessName")
.apply { isAccessible = true }
.invoke(null) as String
}
}
================================================
FILE: android-test-app/src/main/res/values/strings.xml
================================================
android-test
================================================
FILE: android-test-app/src/main/res/xml/network_security_config.xml
================================================
================================================
FILE: android-test-app/test-proguard-rules.pro
================================================
-dontwarn **
================================================
FILE: build-logic/build.gradle.kts
================================================
plugins {
`kotlin-dsl`
id("com.diffplug.spotless") version "8.4.0"
}
configure {
kotlin {
target("src/**/*.kt")
ktlint()
}
kotlinGradle {
target("*.kts")
targetExclude("build/**/*.kts")
ktlint()
}
}
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
dependencies {
implementation(libs.gradlePlugin.kotlin)
implementation(libs.gradlePlugin.mavenPublish)
implementation(libs.gradlePlugin.dokka)
implementation(libs.gradlePlugin.binaryCompatibilityValidator)
implementation(libs.gradlePlugin.android)
implementation(libs.gradlePlugin.bnd)
implementation(libs.aqute.bnd)
implementation(libs.gradlePlugin.animalsniffer)
implementation(libs.gradlePlugin.spotless)
implementation(libs.gradlePlugin.shadow)
implementation(libs.gradlePlugin.graalvm)
implementation(libs.gradlePlugin.ksp)
implementation(libs.gradlePlugin.mrjar)
implementation(libs.gradlePlugin.tapmoc)
implementation(libs.androidx.lint.gradle)
implementation(libs.kotlin.gradle.plugin.api)
}
================================================
FILE: build-logic/settings.gradle.kts
================================================
@file:Suppress("UnstableApiUsage")
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
google()
}
}
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
rootProject.name = "build-logic"
dependencyResolutionManagement {
repositories {
mavenCentral()
gradlePluginPortal()
google()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
================================================
FILE: build-logic/src/main/kotlin/AlpnVersions.kt
================================================
/*
* Copyright (C) 2021 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// https://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-versions
private fun alpnBootVersionForPatchVersion(patchVersion: Int): String? =
when (patchVersion) {
in 0..24 -> "8.1.0.v20141016"
in 25..30 -> "8.1.2.v20141202"
in 31..50 -> "8.1.3.v20150130"
in 51..59 -> "8.1.4.v20150727"
in 60..64 -> "8.1.5.v20150921"
in 65..70 -> "8.1.6.v20151105"
in 71..77 -> "8.1.7.v20160121"
in 78..101 -> "8.1.8.v20160420"
in 102..111 -> "8.1.9.v20160720"
in 112..120 -> "8.1.10.v20161026"
in 121..160 -> "8.1.11.v20170118"
in 161..181 -> "8.1.12.v20180117"
in 191..242 -> "8.1.13.v20181017"
else -> null
}
/**
* Returns the alpn-boot version specific to this OpenJDK 8 JVM, or null if this is not a Java 8 VM.
* https://github.com/xjdr/xio/blob/master/alpn-boot.gradle
*/
fun alpnBootVersion(): String? {
val version = System.getProperty("alpn.boot.version")
if (version != null) {
return version
}
val javaVersion = System.getProperty("java.version")
val match = "1\\.8\\.0_(\\d+)(-.*)?".toRegex().find(javaVersion) ?: return null
return alpnBootVersionForPatchVersion(match.groupValues.first().toInt())
}
================================================
FILE: build-logic/src/main/kotlin/BndBuildAction.kt
================================================
/*
* Copyright (c) aQute SARL (2000, 2021). All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import aQute.bnd.gradle.BundleTaskExtension
import aQute.bnd.osgi.Builder
import aQute.bnd.osgi.Constants
import aQute.bnd.osgi.Jar
import aQute.bnd.osgi.Processor
import aQute.bnd.version.MavenVersion
import aQute.lib.io.IO
import aQute.lib.utf8properties.UTF8Properties
import java.io.File
import java.util.Properties
import java.util.jar.Manifest
import java.util.zip.ZipFile
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.Task
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.bundling.Jar as GradleJar
import org.gradle.api.tasks.bundling.ZipEntryCompression
/**
* A static BuildAction that does not capture the Task instance, enabling Configuration Cache
* compatibility.
*
* This class is based on bundled code from the BND Gradle Plugin.
* https://github.com/bndtools/bnd/blob/master/gradle-plugins/biz.aQute.bnd.gradle/src/main/java/aQute/bnd/gradle/BundleTaskExtension.java
*/
class BndBuildAction(
private val properties: MapProperty,
private val classpath: ConfigurableFileCollection,
private val sourcepath: FileCollection,
private val bundleSymbolicName: Provider,
private val bundleVersion: Provider,
private val bndfile: RegularFileProperty,
private val bnd: Provider,
private val layout: ProjectLayout,
private val entryCompression: ZipEntryCompression,
private val preserveFileTimestamps: Boolean,
) : Action {
constructor(
extension: BundleTaskExtension,
task: GradleJar,
sourceSet: FileCollection,
) : this(
extension.properties,
extension.classpath,
// Sourcepath default: all source
sourceSet,
// Symbolic name default logic
task.archiveBaseName.zip(task.archiveClassifier) { baseName, classifier ->
if (classifier.isNullOrEmpty()) baseName else "$baseName-$classifier"
},
task.archiveVersion.orElse("0").map { version ->
MavenVersion.parseMavenString(version).osGiVersion.toString()
},
extension.bndfile,
extension.bnd,
task.project.layout,
task.entryCompression,
task.isPreserveFileTimestamps,
)
override fun execute(task: Task) {
task as GradleJar
val temporaryDir = task.temporaryDir
val projectDir = layout.projectDirectory.asFile
val gradleProperties = Properties()
properties.get().forEach { (k, v) ->
if (v is Provider<*>) {
val value = v.getOrNull()
if (value != null) gradleProperties[k] = value
} else {
gradleProperties[k] = v
}
}
// Set default values if not present
if (!gradleProperties.containsKey(Constants.BUNDLE_SYMBOLICNAME)) {
gradleProperties[Constants.BUNDLE_SYMBOLICNAME] = bundleSymbolicName.get()
}
if (!gradleProperties.containsKey(Constants.BUNDLE_VERSION)) {
gradleProperties[Constants.BUNDLE_VERSION] = bundleVersion.get()
}
// Do not capture 'task' in gradleProperties to avoid serialization issues
try {
Builder(Processor(gradleProperties, false)).use { builder ->
val temporaryBndFile = File.createTempFile("bnd", ".bnd", temporaryDir)
IO.writer(temporaryBndFile).use { writer ->
val bndFileVal = bndfile.asFile.getOrNull()
if (bndFileVal != null && bndFileVal.isFile) {
Processor(gradleProperties).let { p ->
p.loadProperties(bndFileVal).store(writer, null)
}
} else {
val bndVal = bnd.getOrElse("")
if (bndVal.isNotEmpty()) {
val props = UTF8Properties()
props.load(bndVal, File(projectDir, "build.gradle.kts"), builder)
props.replaceHere(projectDir).store(writer, null)
}
}
}
builder.setProperties(temporaryBndFile, projectDir)
builder.setProperty("project.output", temporaryDir.canonicalPath)
if (builder.`is`(Constants.NOBUNDLES)) return
val archiveFile = task.archiveFile.get().asFile
val archiveFileName = task.archiveFileName.get()
val archiveCopyFile = File(temporaryDir, archiveFileName)
IO.copy(archiveFile, archiveCopyFile)
val bundleJar = Jar(archiveFileName, archiveCopyFile)
if (builder.getProperty(Constants.REPRODUCIBLE) == null && !preserveFileTimestamps) {
builder.setProperty(Constants.REPRODUCIBLE, "true")
}
if (builder.getProperty(Constants.COMPRESSION) == null) {
builder.setProperty(
Constants.COMPRESSION,
when (entryCompression) {
ZipEntryCompression.STORED -> Jar.Compression.STORE.name
else -> Jar.Compression.DEFLATE.name
},
)
}
bundleJar.updateModified(archiveFile.lastModified(), "time of Jar task generated jar")
bundleJar.manifest = Manifest()
builder.setJar(bundleJar)
val validClasspath = classpath.filter { it.exists() && (it.isDirectory || isZip(it)) }
builder.setProperty("project.buildpath", validClasspath.asPath)
builder.setClasspath(validClasspath.files.toTypedArray())
val validSourcepath = sourcepath.filter { it.exists() }
builder.setProperty("project.sourcepath", validSourcepath.asPath)
builder.setSourcepath(validSourcepath.files.toTypedArray())
val builtJar = builder.build()
if (!builder.isOk) {
builder.getErrors().forEach { task.logger.error("Error: $it") }
builder.getWarnings().forEach { task.logger.warn("Warning: $it") }
throw GradleException("Bundle $archiveFileName has errors")
}
builtJar.write(archiveFile)
archiveFile.setLastModified(System.currentTimeMillis())
}
} catch (e: Exception) {
throw GradleException("Bnd build failed", e)
}
}
private fun isZip(file: File): Boolean =
try {
ZipFile(file).close()
true
} catch (e: Exception) {
false
}
companion object {
/**
* BND is incompatible with Kotlin/Multiplatform because it assumes the JVM source set's name is
* 'main'. Work around this by creating a 'main' source set that forwards to 'jvmMain'.
*/
fun installWorkaround(project: org.gradle.api.Project): org.gradle.api.tasks.SourceSet {
val sourceSets =
project.extensions
.getByType(org.gradle.api.plugins.JavaPluginExtension::class.java)
.sourceSets
val existingMain = sourceSets.findByName("main")
if (existingMain != null) return existingMain
val jvmMainSourceSet = sourceSets.getByName("jvmMain")
val mainSourceSet =
object : org.gradle.api.tasks.SourceSet by jvmMainSourceSet {
override fun getName() = "main"
override fun getProcessResourcesTaskName() = "${jvmMainSourceSet.processResourcesTaskName}ForFakeMain"
override fun getCompileJavaTaskName() = "${jvmMainSourceSet.compileJavaTaskName}ForFakeMain"
override fun getClassesTaskName() = "${jvmMainSourceSet.classesTaskName}ForFakeMain"
override fun getCompileOnlyConfigurationName(): String = jvmMainSourceSet.compileOnlyConfigurationName + "ForFakeMain"
override fun getCompileClasspathConfigurationName(): String = jvmMainSourceSet.compileClasspathConfigurationName + "ForFakeMain"
override fun getImplementationConfigurationName(): String = jvmMainSourceSet.implementationConfigurationName + "ForFakeMain"
override fun getAnnotationProcessorConfigurationName(): String =
jvmMainSourceSet.annotationProcessorConfigurationName + "ForFakeMain"
override fun getRuntimeClasspathConfigurationName(): String = jvmMainSourceSet.runtimeClasspathConfigurationName + "ForFakeMain"
override fun getRuntimeOnlyConfigurationName(): String = jvmMainSourceSet.runtimeOnlyConfigurationName + "ForFakeMain"
override fun getTaskName(
verb: String?,
target: String?,
) = "${jvmMainSourceSet.getTaskName(verb, target)}ForFakeMain"
}
sourceSets.add(mainSourceSet)
project.tasks.named { it.endsWith("ForFakeMain") }.configureEach { onlyIf { false } }
return mainSourceSet
}
}
}
================================================
FILE: build-logic/src/main/kotlin/JavaModules.kt
================================================
/*
* Copyright (C) 2025 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import me.champeau.mrjar.MultiReleaseExtension
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.jvm.toolchain.JavaToolchainService
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.named
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
fun Project.applyJavaModules(
moduleName: String,
defaultVersion: Int = 8,
javaModuleVersion: Int = 9,
enableValidation: Boolean = true,
) {
plugins.apply("me.champeau.mrjar")
configure {
targetVersions(defaultVersion, javaModuleVersion)
}
tasks.named("compileJava9Java").configure {
val compileKotlinTask = tasks.getByName("compileKotlin") as KotlinJvmCompile
dependsOn(compileKotlinTask)
if (enableValidation) {
compileKotlinTask.source(file("src/main/java9"))
}
// Ignore warnings about using 'requires transitive' on automatic modules.
// not needed when compiling with recent JDKs, e.g. 17
options.compilerArgs.add("-Xlint:-requires-transitive-automatic")
// Patch the compileKotlinJvm output classes into the compilation so exporting packages works correctly.
options.compilerArgs.addAll(
listOf(
"--patch-module",
"$moduleName=${compileKotlinTask.destinationDirectory.get().asFile}",
),
)
classpath = compileKotlinTask.libraries
modularity.inferModulePath.set(true)
val javaToolchains = project.extensions.getByType()
val javaPluginExtension = project.extensions.getByType()
javaCompiler.set(javaToolchains.compilerFor(javaPluginExtension.toolchain))
}
}
================================================
FILE: build-logic/src/main/kotlin/Osgi.kt
================================================
/*
* Copyright (C) 2021 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import aQute.bnd.gradle.BundleTaskExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.bundling.Jar as GradleJar
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.getByName
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.provideDelegate
fun Project.applyOsgi(vararg bndProperties: String) {
plugins.withId("org.jetbrains.kotlin.jvm") { applyOsgi("jar", "osgiApi", bndProperties) }
}
private fun Project.applyOsgi(
jarTaskName: String,
osgiApiConfigurationName: String,
bndProperties: Array,
) {
val osgi = project.sourceSets.create("osgi")
val osgiApi = project.configurations.getByName(osgiApiConfigurationName)
project.dependencies { osgiApi(kotlinOsgi) }
val jarTask = tasks.getByName(jarTaskName)
val bundleExtension =
jarTask.extensions.create(
BundleTaskExtension.NAME,
BundleTaskExtension::class.java,
jarTask,
)
bundleExtension.run {
setClasspath(osgi.compileClasspath + sourceSets["main"].compileClasspath)
properties.empty()
bnd(*bndProperties)
}
jarTask.doLast(
"buildBundle",
BndBuildAction(bundleExtension, jarTask, sourceSets["main"].allSource),
)
}
fun Project.applyOsgiMultiplatform(vararg bndProperties: String) {
// BND is incompatible with Kotlin/Multiplatform because it assumes the JVM source set's
// name is
// 'main'. Work around this by creating a 'main' source set that forwards to 'jvmMain'.
//
// The forwarding SourceSet also needs to fake out some task names to prevent them from
// being
// registered twice.
//
// https://github.com/bndtools/bnd/issues/6590
val mainSourceSet = BndBuildAction.installWorkaround(project)
val osgiApi = configurations.create("osgiApi")
dependencies { osgiApi(kotlinOsgi) }
tasks.named("jvmJar").configure {
val bundleExtension =
extensions.create(
BundleTaskExtension.NAME,
BundleTaskExtension::class.java,
this,
)
val osgiApiArtifacts = osgiApi.artifacts
val jvmMainClasses = tasks.named("jvmMainClasses").map { it.outputs.files }
bundleExtension.apply {
classpath(osgiApiArtifacts)
classpath(jvmMainClasses)
properties.empty()
bnd(*bndProperties)
}
doLast("buildBundle", BndBuildAction(bundleExtension, this, mainSourceSet.allSource))
}
}
val Project.sourceSets: SourceSetContainer
get() = (this as ExtensionAware).extensions["sourceSets"] as SourceSetContainer
private val Project.kotlinOsgi: MinimalExternalModuleDependency
get() =
extensions
.getByType(VersionCatalogsExtension::class.java)
.named("libs")
.findLibrary("kotlin.stdlib.osgi")
.get()
.get()
================================================
FILE: build-logic/src/main/kotlin/okhttp.base-conventions.gradle.kts
================================================
import org.gradle.api.artifacts.VersionCatalogsExtension
import okhttp3.buildsupport.platform
import okhttp3.buildsupport.testJavaVersion
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
val libs = extensions.getByType().named("libs")
group = "com.squareup.okhttp3"
version = "5.4.0-SNAPSHOT"
val platform = project.platform
val testJavaVersion = project.testJavaVersion
// Friend configurations - moved here to be shared across all modules (including android-test)
val friendsApi = configurations.maybeCreate("friendsApi").apply {
isCanBeResolved = true
isCanBeConsumed = false
isTransitive = true
}
val friendsImplementation = configurations.maybeCreate("friendsImplementation").apply {
isCanBeResolved = true
isCanBeConsumed = false
isTransitive = false
}
val friendsTestImplementation = configurations.maybeCreate("friendsTestImplementation").apply {
isCanBeResolved = true
isCanBeConsumed = false
isTransitive = false
}
configurations.configureEach {
if (name == "implementation") {
extendsFrom(friendsApi, friendsImplementation)
}
if (name == "api") {
extendsFrom(friendsApi)
}
if (name == "testImplementation") {
extendsFrom(friendsTestImplementation)
}
}
tasks.withType().configureEach {
friendPaths.from(friendsApi.incoming.artifactView { }.files)
friendPaths.from(friendsImplementation.incoming.artifactView { }.files)
friendPaths.from(friendsTestImplementation.incoming.artifactView { }.files)
}
val resolvableConfigurations = configurations.filter { it.isCanBeResolved }
tasks.register("downloadDependencies") {
description = "Download all dependencies to the Gradle cache"
doLast {
for (configuration in resolvableConfigurations) {
configuration.files
}
}
}
normalization {
runtimeClasspath {
metaInf {
ignoreAttribute("Bnd-LastModified")
}
}
}
================================================
FILE: build-logic/src/main/kotlin/okhttp.dokka-multimodule-conventions.gradle.kts
================================================
val okhttpDokka: String? by project
val dokkaBuild = okhttpDokka?.toBoolean() == true
if (dokkaBuild) {
apply(plugin = "org.jetbrains.dokka")
dependencies {
add("dokka", project(":okhttp"))
add("dokka", project(":okhttp-brotli"))
add("dokka", project(":okhttp-coroutines"))
add("dokka", project(":okhttp-dnsoverhttps"))
add("dokka", project(":okhttp-java-net-cookiejar"))
add("dokka", project(":logging-interceptor"))
add("dokka", project(":okhttp-sse"))
add("dokka", project(":okhttp-tls"))
add("dokka", project(":okhttp-urlconnection"))
add("dokka", project(":okhttp-zstd"))
add("dokka", project(":mockwebserver"))
add("dokka", project(":mockwebserver3"))
add("dokka", project(":mockwebserver3-junit4"))
add("dokka", project(":mockwebserver3-junit5"))
}
}
================================================
FILE: build-logic/src/main/kotlin/okhttp.jvm-conventions.gradle.kts
================================================
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import tapmoc.TapmocExtension
import tapmoc.configureKotlinCompatibility
plugins {
id("okhttp.base-conventions")
id("com.gradleup.tapmoc")
}
val libs = extensions.getByType().named("libs")
fun library(alias: String) = libs.findLibrary(alias).get().get().let {
"${it.module.group}:${it.module.name}:${it.versionConstraint.requiredVersion}"
}
fun version(alias: String) = libs.findVersion(alias).get().toString()
extensions.configure {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
// Introduce in a separate change
//configureJavaCompatibility(javaVersion = 8)
configureKotlinCompatibility(version = version("kotlinCoreLibrariesVersion"))
tasks.withType {
options.encoding = Charsets.UTF_8.toString()
if (name.contains("Java9")) {
sourceCompatibility = "9"
targetCompatibility = "9"
} else {
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
}
}
tasks.withType {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_1_8)
freeCompilerArgs.addAll(
"-Xjvm-default=all",
"-Xexpect-actual-classes",
)
}
}
extensions.configure {
// Fail the build if any api dependency exposes incompatible Kotlin metadata, Kotlin stdlib, or Java bytecode version.
checkDependencies()
}
================================================
FILE: build-logic/src/main/kotlin/okhttp.publish-conventions.gradle.kts
================================================
import com.vanniktech.maven.publish.JavadocJar
import com.vanniktech.maven.publish.KotlinJvm
import com.vanniktech.maven.publish.KotlinMultiplatform
import com.vanniktech.maven.publish.MavenPublishBaseExtension
import kotlinx.validation.ApiValidationExtension
import kotlinx.validation.KotlinApiBuildTask
import kotlinx.validation.KotlinApiCompareTask
plugins {
id("com.vanniktech.maven.publish.base")
id("binary-compatibility-validator")
}
configure {
publishToMavenCentral(automaticRelease = true)
signAllPublications()
pom {
name.set(project.name)
description.set("Square’s meticulous HTTP client for Java and Kotlin.")
url.set("https://square.github.io/okhttp/")
licenses {
license {
name.set("The Apache Software License, Version 2.0")
url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
distribution.set("repo")
}
}
scm {
connection.set("scm:git:https://github.com/square/okhttp.git")
developerConnection.set("scm:git:ssh://git@github.com/square/okhttp.git")
url.set("https://github.com/square/okhttp")
}
developers {
developer {
name.set("Square, Inc.")
}
}
}
if (project.name == "okhttp") {
configure(KotlinMultiplatform(javadocJar = JavadocJar.Empty()))
} else if (plugins.hasPlugin(JavaBasePlugin::class.java)) {
configure(KotlinJvm(javadocJar = JavadocJar.Empty()))
}
}
configure {
ignoredPackages += "okhttp3.logging.internal"
ignoredPackages += "mockwebserver3.internal"
ignoredPackages += "okhttp3.internal"
ignoredPackages += "mockwebserver3.junit5.internal"
ignoredPackages += "okhttp3.brotli.internal"
ignoredPackages += "okhttp3.sse.internal"
ignoredPackages += "okhttp3.tls.internal"
}
if (project.name == "okhttp") {
// Workaround for https://github.com/Kotlin/binary-compatibility-validator/issues/312
val apiBuild = tasks.register("androidApiBuild") {
outputApiFile = project.layout.buildDirectory.file("${this.name}/okhttp.api")
inputClassesDirs.from(tasks.getByName("compileAndroidMain").outputs)
}
val apiCheck = tasks.register("androidApiCheck") {
group = "verification"
projectApiFile = project.file("api/android/okhttp.api")
generatedApiFile = apiBuild.flatMap(KotlinApiBuildTask::outputApiFile)
}
val apiDump = tasks.register("androidApiDump") {
from(apiBuild.flatMap(KotlinApiBuildTask::outputApiFile))
destinationDir = project.file("api/android")
}
tasks.named("apiDump").configure { dependsOn(apiDump) }
tasks.named("apiCheck").configure { dependsOn(apiCheck) }
}
================================================
FILE: build-logic/src/main/kotlin/okhttp.quality-conventions.gradle.kts
================================================
import com.vanniktech.maven.publish.MavenPublishBaseExtension
import ru.vyarus.gradle.plugin.animalsniffer.AnimalSnifferExtension
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.withType
import ru.vyarus.gradle.plugin.animalsniffer.AnimalSniffer
plugins {
id("okhttp.base-conventions")
id("checkstyle")
id("ru.vyarus.animalsniffer")
id("com.android.lint")
id("com.diffplug.spotless")
}
val libs = extensions.getByType().named("libs")
fun library(alias: String) = libs.findLibrary(alias).get().get().let {
"${it.module.group}:${it.module.name}:${it.versionConstraint.requiredVersion}"
}
fun version(alias: String) = libs.findVersion(alias).get().toString()
tasks.withType().configureEach {
exclude("**/CipherSuite.java")
}
val checkstyleConfig = configurations.maybeCreate("checkstyleConfig")
dependencies {
add("checkstyleConfig", library("checkstyle")) {
isTransitive = false
}
}
configure {
config = resources.text.fromArchiveEntry(checkstyleConfig, "google_checks.xml")
toolVersion = version("checkstyle")
val sourceSets = project.extensions.findByType()
val main = sourceSets?.findByName("main") ?: sourceSets?.findByName("jvmMain")
if (main != null) {
this.sourceSets = listOf(main)
}
}
val androidSignature = configurations.maybeCreate("androidSignature")
val jvmSignature = configurations.maybeCreate("jvmSignature")
configure {
annotation = "okhttp3.internal.SuppressSignatureCheck"
val sourceSets = project.extensions.findByType()
val main = sourceSets?.findByName("main") ?: sourceSets?.findByName("jvmMain")
if (main != null) {
this.sourceSets = listOf(main)
}
signatures = androidSignature + jvmSignature
failWithoutSignatures = false
}
// Default to only published modules
project.tasks.withType {
val hasMavenPublish = project.extensions.findByType() != null
onlyIf {
hasMavenPublish
}
}
dependencies {
// Logic for signatures should be moved to the applying module or configured via extension
// For now, we'll keep the standard ones and allow modules to add more
androidSignature(library("signature-android-apilevel21")) { artifact { type = "signature" } }
jvmSignature(library("codehaus-signature-java18")) { artifact { type = "signature" } }
"lintChecks"(library("androidx-lint-gradle"))
}
configure {
xmlReport = true
checkDependencies = true
}
configure {
kotlin {
target("src/**/*.kt")
targetExclude("**/kotlinTemplates/**/*.kt")
ktlint()
suppressLintsFor {
step = "ktlint"
shortCode = "standard:mixed-condition-operators"
}
}
kotlinGradle {
target("*.kts")
ktlint()
}
}
================================================
FILE: build-logic/src/main/kotlin/okhttp.testing-conventions.gradle.kts
================================================
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.artifacts.VersionCatalogsExtension
import okhttp3.buildsupport.platform
import okhttp3.buildsupport.testJavaVersion
plugins {
id("okhttp.base-conventions")
}
val libs = extensions.getByType().named("libs")
fun library(alias: String) = libs.findLibrary(alias).get().get().let {
"${it.module.group}:${it.module.name}:${it.versionConstraint.requiredVersion}"
}
val testRuntimeOnly = configurations.maybeCreate("testRuntimeOnly")
dependencies {
testRuntimeOnly(library("junit-jupiter-engine"))
testRuntimeOnly(library("junit-vintage-engine"))
testRuntimeOnly(library("junit-platform-launcher"))
}
tasks.withType {
useJUnitPlatform()
jvmArgs("-Dokhttp.platform=$platform")
if (platform == "loom") {
jvmArgs("-Djdk.tracePinnedThreads=short")
}
if (platform == "openjsse") {
if (testJavaVersion > 8) {
throw GradleException("OpenJSSE is only supported on Java 8")
}
}
val javaToolchains = project.extensions.getByType()
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(testJavaVersion))
})
maxParallelForks = Runtime.getRuntime().availableProcessors() * 2
testLogging {
exceptionFormat = TestExceptionFormat.FULL
}
systemProperty("okhttp.platform", platform)
systemProperty("junit.jupiter.extensions.autodetection.enabled", "true")
}
tasks.withType().configureEach {
environment("OKHTTP_ROOT", rootDir)
}
plugins.withId("org.jetbrains.kotlin.jvm") {
val test = tasks.named("test")
tasks.register("jvmTest") {
description = "Get 'gradlew jvmTest' to run the tests of JVM-only modules"
dependsOn(test)
}
}
================================================
FILE: build-logic/src/main/kotlin/okhttp3/buildsupport/OkHttpBuildUtils.kt
================================================
/*
* Copyright (c) 2026 OkHttp Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.buildsupport
import org.gradle.api.Project
val Project.platform: String
get() = findProperty("okhttp.platform")?.toString() ?: "jdk9"
val Project.testJavaVersion: Int
get() = findProperty("test.java.version")?.toString()?.toInt() ?: 21
val Project.androidBuild: Boolean
get() = findProperty("androidBuild")?.toString()?.toBoolean() ?: false
val Project.alpnBootVersion: String?
get() = findProperty("alpn.boot.version")?.toString()
================================================
FILE: build.gradle.kts
================================================
plugins {
alias(libs.plugins.spotless) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.dokka) apply false
alias(libs.plugins.maven.publish) apply false
alias(libs.plugins.binary.compatibility.validator) apply false
alias(libs.plugins.animalsniffer) apply false
alias(libs.plugins.android.junit5) apply false
alias(libs.plugins.shadow) apply false
alias(libs.plugins.graalvm) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.burst) apply false
id("okhttp.dokka-multimodule-conventions")
}
tasks.wrapper {
distributionType = Wrapper.DistributionType.ALL
}
================================================
FILE: container-tests/README.md
================================================
OkHttp Container Tests
======================
This module contains tests against other services
via containers.
================================================
FILE: container-tests/build.gradle.kts
================================================
plugins {
kotlin("jvm")
id("okhttp.base-conventions")
}
import okhttp3.buildsupport.platform
import okhttp3.buildsupport.testJavaVersion
val platform = project.platform
val testJavaVersion = project.testJavaVersion
tasks.withType {
useJUnitPlatform()
val isCi = providers.environmentVariable("CI")
val containerTests = providers.gradleProperty("containerTests")
onlyIf("By default not in CI") {
!isCi.isPresent
|| (containerTests.isPresent && containerTests.get().toBoolean())
}
jvmArgs(
"-Dokhttp.platform=$platform",
)
if (platform == "loom") {
jvmArgs(
"-Djdk.tracePinnedThreads=short",
)
}
val javaToolchains = project.extensions.getByType()
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(testJavaVersion))
})
}
dependencies {
api(projects.okhttp)
testImplementation(projects.okhttpTestingSupport)
testImplementation(libs.junit.jupiter.api)
testImplementation(libs.junit.jupiter.params)
testImplementation(libs.junit.jupiter.engine)
testImplementation(libs.assertk)
testImplementation(libs.testcontainers)
testImplementation(libs.mockserver)
testImplementation(libs.mockserver.client)
testImplementation(libs.testcontainers.junit5)
}
================================================
FILE: container-tests/src/test/java/okhttp3/containers/BasicLoomTest.kt
================================================
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.containers
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.isEmpty
import assertk.assertions.isNotEmpty
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import okhttp3.Dispatcher
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.containers.BasicMockServerTest.Companion.MOCKSERVER_IMAGE
import okhttp3.containers.BasicMockServerTest.Companion.trustMockServer
import okhttp3.testing.PlatformRule
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
import org.junit.jupiter.api.parallel.Isolated
import org.mockserver.client.MockServerClient
import org.mockserver.model.HttpRequest.request
import org.mockserver.model.HttpResponse.response
import org.testcontainers.containers.MockServerContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
@Testcontainers
@Isolated
class BasicLoomTest {
@JvmField
@RegisterExtension
val platform = PlatformRule()
// Use mock server so we are strictly testing OkHttp client only in this test.
// We should test MockWebServer later.
@Container
val mockServer: MockServerContainer = MockServerContainer(MOCKSERVER_IMAGE)
val capturedOut = ByteArrayOutputStream()
private lateinit var executor: ExecutorService
private lateinit var client: OkHttpClient
@BeforeEach
fun setUp() {
platform.assumeLoom()
assertThat(System.getProperty("jdk.tracePinnedThreads")).isNotEmpty()
client =
OkHttpClient
.Builder()
.trustMockServer()
.dispatcher(Dispatcher(newVirtualThreadPerTaskExecutor()))
.build()
executor = newVirtualThreadPerTaskExecutor()
// Capture non-deterministic but probable sysout warnings of pinned threads
// https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html
System.setOut(PrintStream(capturedOut))
}
@AfterEach
fun checkForPinning() {
assertThat(capturedOut.toString()).isEmpty()
}
private fun newVirtualThreadPerTaskExecutor(): ExecutorService =
Executors::class.java.getMethod("newVirtualThreadPerTaskExecutor").invoke(null) as ExecutorService
@Test
fun testHttpsRequest() {
MockServerClient(mockServer.host, mockServer.serverPort).use { mockServerClient ->
mockServerClient
.`when`(
request()
.withPath("/person")
.withQueryStringParameter("name", "peter"),
).respond(response().withBody("Peter the person!"))
val results =
(1..20).map {
executor.submit {
val response =
client.newCall(Request((mockServer.secureEndpoint + "/person?name=peter").toHttpUrl())).execute()
val body = response.body.string()
assertThat(body).contains("Peter the person")
}
}
results.forEach {
it.get()
}
}
}
}
================================================
FILE: container-tests/src/test/java/okhttp3/containers/BasicMockServerTest.kt
================================================
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.containers
import assertk.assertThat
import assertk.assertions.contains
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import org.junit.jupiter.api.Test
import org.mockserver.client.MockServerClient
import org.mockserver.configuration.Configuration
import org.mockserver.logging.MockServerLogger
import org.mockserver.model.HttpRequest.request
import org.mockserver.model.HttpResponse.response
import org.mockserver.socket.tls.KeyStoreFactory
import org.testcontainers.containers.MockServerContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import org.testcontainers.utility.DockerImageName
@Testcontainers
class BasicMockServerTest {
@Container
val mockServer: MockServerContainer = MockServerContainer(MOCKSERVER_IMAGE)
val client =
OkHttpClient
.Builder()
.trustMockServer()
.build()
@Test
fun testRequest() {
MockServerClient(mockServer.host, mockServer.serverPort).use { mockServerClient ->
mockServerClient
.`when`(
request()
.withPath("/person")
.withQueryStringParameter("name", "peter"),
).respond(response().withBody("Peter the person!"))
val response = client.newCall(Request((mockServer.endpoint + "/person?name=peter").toHttpUrl())).execute()
assertThat(response.body.string()).contains("Peter the person")
}
}
@Test
fun testHttpsRequest() {
MockServerClient(mockServer.host, mockServer.serverPort).use { mockServerClient ->
mockServerClient
.`when`(
request()
.withPath("/person")
.withQueryStringParameter("name", "peter"),
).respond(response().withBody("Peter the person!"))
val response = client.newCall(Request((mockServer.secureEndpoint + "/person?name=peter").toHttpUrl())).execute()
assertThat(response.body.string()).contains("Peter the person")
}
}
companion object {
val MOCKSERVER_IMAGE: DockerImageName =
DockerImageName
.parse("mockserver/mockserver")
.withTag("mockserver-5.15.0")
fun OkHttpClient.Builder.trustMockServer(): OkHttpClient.Builder =
apply {
val keyStoreFactory = KeyStoreFactory(Configuration.configuration(), MockServerLogger())
val socketFactory = keyStoreFactory.sslContext().socketFactory
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(keyStoreFactory.loadOrCreateKeyStore())
val trustManager = trustManagerFactory.trustManagers.first() as X509TrustManager
sslSocketFactory(socketFactory, trustManager)
}
}
}
================================================
FILE: container-tests/src/test/java/okhttp3/containers/BasicProxyTest.kt
================================================
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.containers
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.isEqualTo
import java.net.HttpURLConnection
import java.net.Proxy
import java.net.URI
import javax.net.ssl.HttpsURLConnection
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.containers.BasicMockServerTest.Companion.MOCKSERVER_IMAGE
import okhttp3.containers.BasicMockServerTest.Companion.trustMockServer
import okio.buffer
import okio.source
import org.junit.jupiter.api.Test
import org.mockserver.client.MockServerClient
import org.mockserver.configuration.Configuration
import org.mockserver.logging.MockServerLogger
import org.mockserver.model.HttpRequest.request
import org.mockserver.model.HttpResponse.response
import org.mockserver.proxyconfiguration.ProxyConfiguration
import org.mockserver.socket.tls.KeyStoreFactory
import org.testcontainers.containers.MockServerContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
@Testcontainers
class BasicProxyTest {
@Container
val mockServer: MockServerContainer =
MockServerContainer(MOCKSERVER_IMAGE)
.withNetworkAliases("mockserver")
@Test
fun testOkHttpDirect() {
testRequest {
val client = OkHttpClient()
val response =
client
.newCall(
Request((mockServer.endpoint + "/person?name=peter").toHttpUrl()),
).execute()
assertThat(response.body.string()).contains("Peter the person")
assertThat(response.protocol).isEqualTo(Protocol.HTTP_1_1)
}
}
@Test
fun testOkHttpProxied() {
testRequest {
it.withProxyConfiguration(ProxyConfiguration.proxyConfiguration(ProxyConfiguration.Type.HTTP, it.remoteAddress()))
val client =
OkHttpClient
.Builder()
.proxy(Proxy(Proxy.Type.HTTP, it.remoteAddress()))
.build()
val response =
client
.newCall(
Request((mockServer.endpoint + "/person?name=peter").toHttpUrl()),
).execute()
assertThat(response.body.string()).contains("Peter the person")
}
}
@Test
fun testOkHttpSecureDirect() {
testRequest {
val client =
OkHttpClient
.Builder()
.trustMockServer()
.build()
val response =
client
.newCall(
Request((mockServer.secureEndpoint + "/person?name=peter").toHttpUrl()),
).execute()
assertThat(response.body.string()).contains("Peter the person")
assertThat(response.protocol).isEqualTo(Protocol.HTTP_2)
}
}
@Test
fun testOkHttpSecureProxiedHttp1() {
testRequest {
val client =
OkHttpClient
.Builder()
.trustMockServer()
.proxy(Proxy(Proxy.Type.HTTP, it.remoteAddress()))
.protocols(listOf(Protocol.HTTP_1_1))
.build()
val response =
client
.newCall(
Request((mockServer.secureEndpoint + "/person?name=peter").toHttpUrl()),
).execute()
assertThat(response.body.string()).contains("Peter the person")
assertThat(response.protocol).isEqualTo(Protocol.HTTP_1_1)
}
}
@Test
fun testUrlConnectionDirect() {
testRequest {
val url = URI(mockServer.endpoint + "/person?name=peter").toURL()
val connection = url.openConnection() as HttpURLConnection
assertThat(
connection.inputStream
.source()
.buffer()
.readUtf8(),
).contains("Peter the person")
}
}
@Test
fun testUrlConnectionPlaintextProxied() {
testRequest {
val proxy =
Proxy(
Proxy.Type.HTTP,
it.remoteAddress(),
)
val url = URI(mockServer.endpoint + "/person?name=peter").toURL()
val connection = url.openConnection(proxy) as HttpURLConnection
assertThat(
connection.inputStream
.source()
.buffer()
.readUtf8(),
).contains("Peter the person")
}
}
@Test
fun testUrlConnectionSecureDirect() {
val keyStoreFactory = KeyStoreFactory(Configuration.configuration(), MockServerLogger())
HttpsURLConnection.setDefaultSSLSocketFactory(keyStoreFactory.sslContext().socketFactory)
testRequest {
val url = URI(mockServer.secureEndpoint + "/person?name=peter").toURL()
val connection = url.openConnection() as HttpURLConnection
assertThat(
connection.inputStream
.source()
.buffer()
.readUtf8(),
).contains("Peter the person")
}
}
@Test
fun testUrlConnectionSecureProxied() {
val keyStoreFactory = KeyStoreFactory(Configuration.configuration(), MockServerLogger())
HttpsURLConnection.setDefaultSSLSocketFactory(keyStoreFactory.sslContext().socketFactory)
testRequest {
val proxy =
Proxy(
Proxy.Type.HTTP,
it.remoteAddress(),
)
val url = URI(mockServer.secureEndpoint + "/person?name=peter").toURL()
val connection = url.openConnection(proxy) as HttpURLConnection
assertThat(
connection.inputStream
.source()
.buffer()
.readUtf8(),
).contains("Peter the person")
}
}
private fun testRequest(function: (MockServerClient) -> Unit) {
MockServerClient(mockServer.host, mockServer.serverPort).use { mockServerClient ->
val request =
request()
.withPath("/person")
.withQueryStringParameter("name", "peter")
mockServerClient
.`when`(
request,
).respond(response().withBody("Peter the person!"))
function(mockServerClient)
}
}
}
================================================
FILE: container-tests/src/test/java/okhttp3/containers/SocksProxyTest.kt
================================================
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.containers
import assertk.assertThat
import assertk.assertions.contains
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.Proxy.Type.SOCKS
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.containers.BasicMockServerTest.Companion.MOCKSERVER_IMAGE
import org.junit.jupiter.api.Test
import org.mockserver.client.MockServerClient
import org.mockserver.model.HttpRequest.request
import org.mockserver.model.HttpResponse.response
import org.testcontainers.containers.GenericContainer
import org.testcontainers.containers.MockServerContainer
import org.testcontainers.containers.Network
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import org.testcontainers.utility.DockerImageName
@Testcontainers
class SocksProxyTest {
val network: Network = Network.newNetwork()
@Container
val mockServer: MockServerContainer =
MockServerContainer(MOCKSERVER_IMAGE)
.withNetwork(network)
.withNetworkAliases("mockserver")
@Container
val socks5Proxy =
GenericContainer(SOCKS5_PROXY)
.withNetwork(network)
.withExposedPorts(1080)
@Test
fun testLocal() {
MockServerClient(mockServer.host, mockServer.serverPort).use { mockServerClient ->
mockServerClient
.`when`(
request()
.withPath("/person")
.withQueryStringParameter("name", "peter"),
).respond(response().withBody("Peter the person!"))
val client =
OkHttpClient
.Builder()
.proxy(Proxy(SOCKS, InetSocketAddress(socks5Proxy.host, socks5Proxy.firstMappedPort)))
.build()
val response =
client
.newCall(
Request("http://mockserver:1080/person?name=peter".toHttpUrl()),
).execute()
assertThat(response.body.string()).contains("Peter the person")
}
}
companion object {
val SOCKS5_PROXY: DockerImageName =
DockerImageName
.parse("serjs/go-socks5-proxy")
.withTag("v0.0.3")
}
}
================================================
FILE: deploy_website.sh
================================================
#!/bin/bash
# The website is built using MkDocs with the Material theme.
# https://squidfunk.github.io/mkdocs-material/
# It requires python3 to run.
set -ex
REPO="git@github.com:square/okhttp.git"
DIR=temp-clone
# Delete any existing temporary website clone
rm -rf $DIR
# Clone the current repo into temp folder
git clone $REPO $DIR
# Replace `git clone` with these lines to hack on the website locally
# cp -a . "../okhttp-website"
# mv "../okhttp-website" "$DIR"
# Move working directory into temp folder
cd $DIR
# Generate the API docs
./gradlew dokkaHtmlMultiModule
mv ./build/dokka/htmlMultiModule docs/5.x
# Copy in special files that GitHub wants in the project root.
cat README.md | grep -v 'project website' > docs/index.md
cp CHANGELOG.md docs/changelogs/changelog.md
cp CONTRIBUTING.md docs/contribute/contributing.md
# Build the site and push the new files up to GitHub
python3 -m venv venv
source venv/bin/activate
pip install mkdocs-material mkdocs-redirects
mkdocs gh-deploy
# Restore Javadocs from 1.x, 2.x, and 3.x.
git checkout gh-pages
git cherry-pick bb229b9dcc9a21a73edbf8d936bea88f52e0a3ff
git cherry-pick c695732f1d4aea103b826876c077fbfea630e244
git push --set-upstream origin gh-pages
# Delete our temp folder
cd ..
rm -rf $DIR
================================================
FILE: docs/assets/css/app.css
================================================
@font-face {
font-family: cash-market;
src: url("https://cash-f.squarecdn.com/static/fonts/cash-market/v2/CashMarket-Regular.woff2") format("woff2");
font-weight: 400;
font-style: normal
}
@font-face {
font-family: cash-market;
src: url("https://cash-f.squarecdn.com/static/fonts/cash-market/v2/CashMarket-Medium.woff2") format("woff2");
font-weight: 500;
font-style: normal
}
@font-face {
font-family: cash-market;
src: url("https://cash-f.squarecdn.com/static/fonts/cash-market/v2/CashMarket-Bold.woff2") format("woff2");
font-weight: 700;
font-style: normal
}
body, input {
font-family: cash-market,"Helvetica Neue",helvetica,sans-serif;
}
.md-typeset h1, .md-typeset h2, .md-typeset h3, .md-typeset h4 {
font-family: cash-market,"Helvetica Neue",helvetica,sans-serif;
line-height: normal;
font-weight: bold;
}
button.dl {
font-weight: 300;
font-size: 25px;
line-height: 40px;
padding: 3px 10px;
display: inline-block;
border-radius: 6px;
color: #f0f0f0;
margin: 5px 0;
width: auto;
}
.logo {
text-align: center;
margin-top: 150px;
}
================================================
FILE: docs/changelogs/changelog_1x.md
================================================
OkHttp 1.x Change Log
=====================
## Version 1.6.0
_2014-05-23_
* Offer bridges to make it easier to migrate from OkHttp 1.x to OkHttp 2.0.
This adds `OkUrlFactory`, `Cache`, and `@Deprecated` annotations for APIs
dropped in 2.0.
## Version 1.5.4
_2014-04-14_
* Drop ALPN support in Android. There's a concurrency bug in all
currently-shipping versions.
* Support asynchronous disconnects by breaking the socket only. This should
prevent flakiness from multiple threads concurrently accessing a stream.
## Version 1.5.3
_2014-03-29_
* Fix bug where the Content-Length header was not always dropped when
following a redirect from a POST to a GET.
* Implement basic support for `Thread.interrupt()`. OkHttp now checks
for an interruption before doing a blocking call. If it is interrupted,
it throws an `InterruptedIOException`.
## Version 1.5.2
_2014-03-17_
* Fix bug where deleting a file that was absent from the `HttpResponseCache`
caused an IOException.
* Fix bug in HTTP/2 where our HPACK decoder wasn't emitting entries in
certain eviction scenarios, leading to dropped response headers.
## Version 1.5.1
_2014-03-11_
* Fix 1.5.0 regression where connections should not have been recycled.
* Fix 1.5.0 regression where transparent Gzip was broken by attempting to
recover from another I/O failure.
* Fix problems where spdy/3.1 headers may not have been compressed properly.
* Fix problems with spdy/3.1 and http/2 where the wrong window size was being
used.
* Fix 1.5.0 regression where conditional cache responses could corrupt the
connection pool.
## Version 1.5.0
_2014-03-07_
##### OkHttp no longer uses the default SSL context.
Applications that want to use the global SSL context with OkHttp should configure their
OkHttpClient instances with the following:
```java
okHttpClient.setSslSocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());
```
A simpler solution is to avoid the shared default SSL socket factory. Instead, if you
need to customize SSL, do so for your specific OkHttpClient instance only.
##### Synthetic headers have changed
Previously OkHttp added a synthetic response header, `OkHttp-Selected-Transport`. It
has been replaced with a new synthetic header, `OkHttp-Selected-Protocol`.
##### Changes
* New: Support for `HTTP-draft-09/2.0`.
* New: Support for `spdy/3.1`. Dropped support for `spdy/3`.
* New: Use ALPN on Android platforms that support it (4.4+)
* New: CacheControl model and parser.
* New: Protocol selection in MockWebServer.
* Fix: Route selection shouldn't use TLS modes that we know will fail.
* Fix: Cache SPDY responses even if the response body is closed prematurely.
* Fix: Use strict timeouts when aborting a download.
* Fix: Support Shoutcast HTTP responses like `ICY 200 OK`.
* Fix: Don't unzip if there isn't a response body.
* Fix: Don't leak gzip streams on redirects.
* Fix: Don't do DNS lookups on invalid hosts.
* Fix: Exhaust the underlying stream when reading gzip streams.
* Fix: Support the `PATCH` method.
* Fix: Support request bodies on `DELETE` method.
* Fix: Drop the `okhttp-protocols` module.
* Internal: Replaced internal byte array buffers with pooled buffers ("OkBuffer").
## Version 1.3.0
_2014-01-11_
* New: Support for "PATCH" HTTP method in client and MockWebServer.
* Fix: Drop `Content-Length` header when redirected from POST to GET.
* Fix: Correctly read cached header entries with malformed header names.
* Fix: Do not directly support any authentication schemes other than "Basic".
* Fix: Respect read timeouts on recycled connections.
* Fix: Transmit multiple cookie values as a single header with delimiter.
* Fix: Ensure `null` is never returned from a connection's `getHeaderFields()`.
* Fix: Persist proper `Content-Encoding` header to cache for GZip responses.
* Fix: Eliminate rare race condition in SPDY streams that would prevent connection reuse.
* Fix: Change HTTP date formats to UTC to conform to RFC2616 section 3.3.
* Fix: Support SPDY header blocks with trailing bytes.
* Fix: Allow `;` as separator for `Cache-Control` header.
* Fix: Correct bug where HTTPS POST requests were always automatically buffered.
* Fix: Honor read timeout when parsing SPDY headers.
## Version 1.2.1
_2013-08-23_
* Resolve issue with 'jar-with-dependencies' artifact creation.
* Fix: Support empty SPDY header values.
## Version 1.2.0
_2013-08-11_
* New APIs on OkHttpClient to set default timeouts for connect and read.
* Fix bug when caching SPDY responses.
* Fix a bug with SPDY plus half-closed streams. (thanks kwuollett)
* Fix a bug in `Content-Length` reporting for gzipped streams in the Apache
HTTP client adapter. (thanks kwuollett)
* Work around the Alcatel `getByInetAddress` bug (thanks k.kocel)
* Be more aggressive about testing pooled sockets before reuse. (thanks
warpspin)
* Include `Content-Type` and `Content-Encoding` in the Apache HTTP client
adapter. (thanks kwuollett)
* Add a media type class to OkHttp.
* Change custom header prefix:
```
X-Android-Sent-Millis is now OkHttp-Sent-Millis
X-Android-Received-Millis is now OkHttp-Received-Millis
X-Android-Response-Source is now OkHttp-Response-Source
X-Android-Selected-Transport is now OkHttp-Selected-Transport
```
* Improve cache invalidation for POST-like requests.
* Bring MockWebServer into OkHttp and teach it SPDY.
## Version 1.1.1
_2013-06-23_
* Fix: ClassCastException when caching responses that were redirected from
HTTP to HTTPS.
## Version 1.1.0
_2013-06-15_
* Fix: Connection reuse was broken for most HTTPS connections due to a bug in
the way the hostname verifier was selected.
* Fix: Locking bug in SpdyConnection.
* Fix: Ignore null header values (for compatibility with HttpURLConnection).
* Add URLStreamHandlerFactory support so that `URL.openConnection()` uses
OkHttp.
* Expose the transport ("http/1.1", "spdy/3", etc.) via magic request headers.
Use `X-Android-Transports` to write the preferred transports and
`X-Android-Selected-Transport` to read the negotiated transport.
## Version 1.0.2
_2013-05-11_
* Fix: Remove use of Java 6-only APIs.
* Fix: Properly handle exceptions from `NetworkInterface` when querying MTU.
* Fix: Ensure MTU has a reasonable default and upper-bound.
## Version 1.0.1
_2013-05-06_
* Correct casing of SSL in method names (`getSslSocketFactory`/`setSslSocketFactory`).
## Version 1.0.0
_2013-05-06_
Initial release.
================================================
FILE: docs/changelogs/changelog_2x.md
================================================
OkHttp 2.x Change Log
=====================
## Version 2.7.5
_2016-02-25_
* Fix: Change the certificate pinner to always build full chains. This
prevents a potential crash when using certificate pinning with the Google
Play Services security provider.
## Version 2.7.4
_2016-02-07_
* Fix: Don't crash when finding the trust manager if the Play Services (GMS)
security provider is installed.
* Fix: The previous release introduced a performance regression on Android,
caused by looking up CA certificates. This is now fixed.
## Version 2.7.3
_2016-02-06_
* Fix: Permit the trusted CA root to be pinned by `CertificatePinner`.
## Version 2.7.2
_2016-01-07_
* Fix: Don't eagerly release stream allocations on cache hits. We might still
need them to handle redirects.
## Version 2.7.1
_2016-01-01_
* Fix: Don't do a health check on newly-created connections. This is
unnecessary work that could put the client in an inconsistent state if the
health check fails.
## Version 2.7.0
_2015-12-13_
* **Rewritten connection management.** Previously OkHttp's connection pool
managed both idle and active connections for HTTP/2, but only idle
connections for HTTP/1.x. With this update the connection pool manages both
idle and active connections for everything. OkHttp now detects and warns on
connections that were allocated but never released, and will enforce HTTP/2
stream limits. This update also fixes `Call.cancel()` to not do I/O on the
calling thread.
* Fix: Don't log gzipped data in the logging interceptor.
* Fix: Don't resolve DNS addresses when connecting through a SOCKS proxy.
* Fix: Drop the synthetic `OkHttp-Selected-Protocol` response header.
* Fix: Support 204 and 205 'No Content' replies in the logging interceptor.
* New: Add `Call.isExecuted()`.
## Version 2.6.0
_2015-11-22_
* **New Logging Interceptor.** The `logging-interceptor` subproject offers
simple request and response logging. It may be configured to log headers and
bodies for debugging. It requires this Maven dependency:
```xml
com.squareup.okhttplogging-interceptor2.6.0
```
Configure basic logging like this:
```java
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
client.networkInterceptors().add(loggingInterceptor);
```
**Warning:** Avoid `Level.HEADERS` and `Level.BODY` in production because
they could leak passwords and other authentication credentials to insecure
logs.
* **WebSocket API now uses `RequestBody` and `ResponseBody` for messages.**
This is a backwards-incompatible API change.
* **The DNS service is now pluggable.** In some situations this may be useful
to manually prioritize specific IP addresses.
* Fix: Don't throw when converting an `HttpUrl` to a `java.net.URI`.
Previously URLs with special characters like `|` and `[` would break when
subjected to URI’s overly-strict validation.
* Fix: Don't re-encode `+` as `%20` in encoded URL query strings. OkHttp
prefers `%20` when doing its own encoding, but will retain `+` when that is
provided.
* Fix: Enforce that callers call `WebSocket.close()` on IO errors. Error
handling in WebSockets is significantly improved.
* Fix: Don't use SPDY/3 style header concatenation for HTTP/2 request headers.
This could have corrupted requests where multiple headers had the same name,
as in cookies.
* Fix: Reject bad characters in the URL hostname. Previously characters like
`\0` would cause a late crash when building the request.
* Fix: Allow interceptors to change the request method.
* Fix: Don’t use the request's `User-Agent` or `Proxy-Authorization` when
connecting to an HTTPS server via an HTTP tunnel. The `Proxy-Authorization`
header was being leaked to the origin server.
* Fix: Digits may be used in a URL scheme.
* Fix: Improve connection timeout recovery.
* Fix: Recover from `getsockname` crashes impacting Android releases prior to
4.2.2.
* Fix: Drop partial support for HTTP/1.0. Previously OkHttp would send
`HTTP/1.0` on connections after seeing a response with `HTTP/1.0`. The fixed
behavior is consistent with Firefox and Chrome.
* Fix: Allow a body in `OPTIONS` requests.
* Fix: Don't percent-encode non-ASCII characters in URL fragments.
* Fix: Handle null fragments.
* Fix: Don’t crash on interceptors that throw `IOException` before a
connection is attempted.
* New: Support [WebDAV][webdav] HTTP methods.
* New: Buffer WebSocket frames for better performance.
* New: Drop support for `TLS_DHE_DSS_WITH_AES_128_CBC_SHA`, our only remaining
DSS cipher suite. This is consistent with Firefox and Chrome which have also
dropped these cipher suite.
## Version 2.5.0
_2015-08-25_
* **Timeouts now default to 10 seconds.** Previously we defaulted to never
timing out, and that was a lousy policy. If establishing a connection,
reading the next byte from a connection, or writing the next byte to a
connection takes more than 10 seconds to complete, you’ll need to adjust
the timeouts manually.
* **OkHttp now rejects request headers that contain invalid characters.** This
includes potential security problems (newline characters) as well as simple
non-ASCII characters (including international characters and emoji).
* **Call canceling is more reliable.** We had a bug where a socket being
connected wasn't being closed when the application used `Call.cancel()`.
* **Changing a HttpUrl’s scheme now tracks the default port.** We had a bug
where changing a URL from `http` to `https` would leave it on port 80.
* **Okio has been updated to 1.6.0.**
```xml
com.squareup.okiookio1.6.0
```
* New: `Cache.initialize()`. Call this on a background thread to eagerly
initialize the response cache.
* New: Fold `MockWebServerRule` into `MockWebServer`. This makes it easier to
write JUnit tests with `MockWebServer`. The `MockWebServer` library now
depends on JUnit, though it continues to work with all testing frameworks.
* Fix: `FormEncodingBuilder` is now consistent with browsers in which
characters it escapes. Previously we weren’t percent-encoding commas,
parens, and other characters.
* Fix: Relax `FormEncodingBuilder` to support building empty forms.
* Fix: Timeouts throw `SocketTimeoutException`, not `InterruptedIOException`.
* Fix: Change `MockWebServer` to use the same logic as OkHttp when determining
whether an HTTP request permits a body.
* Fix: `HttpUrl` now uses the canonical form for IPv6 addresses.
* Fix: Use `HttpUrl` internally.
* Fix: Recover from Android 4.2.2 EBADF crashes.
* Fix: Don't crash with an `IllegalStateException` if an HTTP/2 or SPDY
write fails, leaving the connection in an inconsistent state.
* Fix: Make sure the default user agent is ASCII.
## Version 2.4.0
_2015-05-22_
* **Forbid response bodies on HTTP 204 and 205 responses.** Webservers that
return such malformed responses will now trigger a `ProtocolException` in
the client.
* **WebSocketListener has incompatible changes.** The `onOpen()` method is now
called on the reader thread, so implementations must return before further
websocket messages will be delivered. The `onFailure()` method now includes
an HTTP response if one was returned.
## Version 2.4.0-RC1
_2015-05-16_
* **New HttpUrl API.** It's like `java.net.URL` but good. Note that
`Request.Builder.url()` now throws `IllegalArgumentException` on malformed
URLs. (Previous releases would throw a `MalformedURLException` when calling
a malformed URL.)
* **We've improved connect failure recovery.** We now differentiate between
setup, connecting, and connected and implement appropriate recovery rules
for each. This changes `Address` to no longer use `ConnectionSpec`. (This is
an incompatible API change).
* **`FormEncodingBuilder` now uses `%20` instead of `+` for encoded spaces.**
Both are permitted-by-spec, but `%20` requires fewer special cases.
* **Okio has been updated to 1.4.0.**
```xml
com.squareup.okiookio1.4.0
```
* **`Request.Builder` no longer accepts null if a request body is required.**
Passing null will now fail for request methods that require a body. Instead
use an empty body such as this one:
```java
RequestBody.create(null, new byte[0]);
```
* **`CertificatePinner` now supports wildcard hostnames.** As always with
certificate pinning, you must be very careful to avoid [bricking][brick]
your app. You'll need to pin both the top-level domain and the `*.` domain
for full coverage.
```java
client.setCertificatePinner(new CertificatePinner.Builder()
.add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
.add("*.publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
.add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
.add("*.publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
.add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
.add("*.publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
.add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
.add("*.publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
.build());
```
* **Interceptors lists are now deep-copied by `OkHttpClient.clone()`.**
Previously clones shared interceptors, which made it difficult to customize
the interceptors on a request-by-request basis.
* New: `Headers.toMultimap()`.
* New: `RequestBody.create(MediaType, ByteString)`.
* New: `ConnectionSpec.isCompatible(SSLSocket)`.
* New: `Dispatcher.getQueuedCallCount()` and
`Dispatcher.getRunningCallCount()`. These can be useful in diagnostics.
* Fix: OkHttp no longer shares timeouts between pooled connections. This was
causing some applications to crash when connections were reused.
* Fix: `OkApacheClient` now allows an empty `PUT` and `POST`.
* Fix: Websockets no longer rebuffer socket streams.
* Fix: Websockets are now better at handling close frames.
* Fix: Content type matching is now case insensitive.
* Fix: `Vary` headers are not lost with `android.net.http.HttpResponseCache`.
* Fix: HTTP/2 wasn't enforcing stream timeouts when writing the underlying
connection. Now it is.
* Fix: Never return null on `call.proceed()`. This was a bug in call
cancelation.
* Fix: When a network interceptor mutates a request, that change is now
reflected in `Response.networkResponse()`.
* Fix: Badly-behaving caches now throw a checked exception instead of a
`NullPointerException`.
* Fix: Better handling of uncaught exceptions in MockWebServer with HTTP/2.
## Version 2.3.0
_2015-03-16_
* **HTTP/2 support.** We've done interop testing and haven't seen any
problems. HTTP/2 support has been a big effort and we're particularly
thankful to Adrian Cole who has helped us to reach this milestone.
* **RC4 cipher suites are no longer supported by default.** To connect to
old, obsolete servers relying on these cipher suites, you must create a
custom `ConnectionSpec`.
* **Beta WebSockets support.**. The `okhttp-ws` subproject offers a new
websockets client. Please try it out! When it's ready we intend to include
it with the core OkHttp library.
* **Okio updated to 1.3.0.**
```xml
com.squareup.okiookio1.3.0
```
* **Fix: improve parallelism of async requests.** OkHttp's Dispatcher had a
misconfigured `ExecutorService` that limited the number of worker threads.
If you're using `Call.enqueue()` this update should significantly improve
request concurrency.
* **Fix: Lazily initialize the response cache.** This avoids strict mode
warnings when initializing OkHttp on Android‘s main thread.
* **Fix: Disable ALPN on Android 4.4.** That release of the feature was
unstable and prone to native crashes in the underlying OpenSSL code.
* Fix: Don't send both `If-None-Match` and `If-Modified-Since` cache headers
when both are applicable.
* Fix: Fail early when a port is out of range.
* Fix: Offer `Content-Length` headers for multipart request bodies.
* Fix: Throw `UnknownServiceException` if a cleartext connection is attempted
when explicitly forbidden.
* Fix: Throw a `SSLPeerUnverifiedException` when host verification fails.
* Fix: MockWebServer explicitly closes sockets. (On some Android releases,
closing the input stream and output stream of a socket is not sufficient.
* Fix: Buffer outgoing HTTP/2 frames to limit how many outgoing frames are
created.
* Fix: Avoid crashing when cache writing fails due to a full disk.
* Fix: Improve caching of private responses.
* Fix: Update cache-by-default response codes.
* Fix: Reused `Request.Builder` instances no longer hold stale URL fields.
* New: ConnectionSpec can now be configured to use the SSL socket's default
cipher suites. To use, set the cipher suites to `null`.
* New: Support `DELETE` with a request body.
* New: `Headers.of(Map)` creates headers from a Map.
## Version 2.2.0
_2014-12-30_
* **`RequestBody.contentLength()` now throws `IOException`.**
This is a source-incompatible change. If you have code that calls
`RequestBody.contentLength()`, your compile will break with this
update. The change is binary-compatible, however: code compiled
for OkHttp 2.0 and 2.1 will continue to work with this update.
* **`COMPATIBLE_TLS` no longer supports SSLv3.** In response to the
[POODLE](https://googleonlinesecurity.blogspot.ca/2014/10/this-poodle-bites-exploiting-ssl-30.html)
vulnerability, OkHttp no longer offers SSLv3 when negotiation an
HTTPS connection. If you continue to need to connect to webservers
running SSLv3, you must manually configure your own `ConnectionSpec`.
* **OkHttp now offers interceptors.** Interceptors are a powerful mechanism
that can monitor, rewrite, and retry calls. The [interceptors doc][interceptors] is a full
introduction to this new API.
* New: APIs to iterate and selectively clear the response cache.
* New: Support for SOCKS proxies.
* New: Support for `TLS_FALLBACK_SCSV`.
* New: Update HTTP/2 support to `h2-16` and `hpack-10`.
* New: APIs to prevent retrying non-idempotent requests.
* Fix: Drop NPN support. Going forward we support ALPN only.
* Fix: The hostname verifier is now strict. This is consistent with the hostname
verifier in modern browsers.
* Fix: Improve `CONNECT` handling for misbehaving HTTP proxies.
* Fix: Don't retry requests that failed due to timeouts.
* Fix: Cache 302s and 308s that include appropriate response headers.
* Fix: Improve pooling of connections that use proxy selectors.
* Fix: Don't leak connections when using ALPN on the desktop.
* Fix: Update Jetty ALPN to `7.1.2.v20141202` (Java 7) and `8.1.2.v20141202` (Java 8).
This fixes a bug in resumed TLS sessions where the wrong protocol could be
selected.
* Fix: Don't crash in SPDY and HTTP/2 when disconnecting before connecting.
* Fix: Avoid a reverse DNS-lookup for a numeric proxy address
* Fix: Resurrect http/2 frame logging.
* Fix: Limit to 20 authorization attempts.
## Version 2.1.0
_2014-11-11_
* New: Typesafe APIs for interacting with cipher suites and TLS versions.
* Fix: Don't crash when mixing authorization challenges with upload retries.
## Version 2.1.0-RC1
_2014-11-04_
* **OkHttp now caches private responses**. We've changed from a shared cache
to a private cache, and will now store responses that use an `Authorization`
header. This means OkHttp's cache shouldn't be used on middleboxes that sit
between user agents and the origin server.
* **TLS configuration updated.** OkHttp now explicitly enables TLSv1.2,
TLSv1.1 and TLSv1.0 where they are supported. It will continue to perform
only one fallback, to SSLv3. Applications can now configure this with the
`ConnectionSpec` class.
To disable TLS fallback:
```java
client.setConnectionSpecs(Arrays.asList(
ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT));
```
To disable cleartext connections, permitting `https` URLs only:
```java
client.setConnectionSpecs(Arrays.asList(
ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS));
```
* **New cipher suites.** Please confirm that your webservers are reachable
with this limited set of cipher suites.
```
Android
Name Version
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 5.0
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 5.0
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 5.0
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 4.0
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 4.0
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 4.0
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 4.0
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA 4.0
TLS_ECDHE_RSA_WITH_RC4_128_SHA 4.0
TLS_DHE_RSA_WITH_AES_128_CBC_SHA 2.3
TLS_DHE_DSS_WITH_AES_128_CBC_SHA 2.3
TLS_DHE_RSA_WITH_AES_256_CBC_SHA 2.3
TLS_RSA_WITH_AES_128_GCM_SHA256 5.0
TLS_RSA_WITH_AES_128_CBC_SHA 2.3
TLS_RSA_WITH_AES_256_CBC_SHA 2.3
SSL_RSA_WITH_3DES_EDE_CBC_SHA 2.3 (Deprecated in 5.0)
SSL_RSA_WITH_RC4_128_SHA 2.3
SSL_RSA_WITH_RC4_128_MD5 2.3 (Deprecated in 5.0)
```
* **Okio updated to 1.0.1.**
```xml
com.squareup.okiookio1.0.1
```
* **New APIs to permit easy certificate pinning.** Be warned, certificate
pinning is dangerous and could prevent your application from trusting your
server!
* **Cache improvements.** This release fixes some severe cache problems
including a bug where the cache could be corrupted upon certain access
patterns. We also fixed a bug where the cache was being cleared due to a
corrupted journal. We've added APIs to configure a request's `Cache-Control`
headers, and to manually clear the cache.
* **Request cancellation fixes.** This update fixes a bug where synchronous
requests couldn't be canceled by tag. This update avoids crashing when
`onResponse()` throws an `IOException`. That failure will now be logged
instead of notifying the thread's uncaught exception handler. We've added a
new API, `Call.isCanceled()` to check if a call has been canceled.
* New: Update `MultipartBuilder` to support content length.
* New: Make it possible to mock `OkHttpClient` and `Call`.
* New: Update to h2-14 and hpack-9.
* New: OkHttp includes a user-agent by default, like `okhttp/2.1.0-RC1`.
* Fix: Handle response code `308 Permanent Redirect`.
* Fix: Don't skip the callback if a call is canceled.
* Fix: Permit hostnames with underscores.
* Fix: Permit overriding the content-type in `OkApacheClient`.
* Fix: Use the socket factory for direct connections.
* Fix: Honor `OkUrlFactory` APIs that disable redirects.
* Fix: Don't crash on concurrent modification of `SPDY` SPDY settings.
## Version 2.0.0
This release commits to a stable 2.0 API. Read the 2.0.0-RC1 changes for advice
on upgrading from 1.x to 2.x.
_2014-06-21_
* **API Change**: Use `IOException` in `Callback.onFailure()`. This is
a source-incompatible change, and is different from OkHttp 2.0.0-RC2 which
used `Throwable`.
* Fix: Fixed a caching bug where we weren't storing rewritten request headers
like `Accept-Encoding`.
* Fix: Fixed bugs in handling the SPDY window size. This was stalling certain
large downloads
* Update the language level to Java 7. (OkHttp requires Android 2.3+ or Java 7+.)
## Version 2.0.0-RC2
_2014-06-11_
This update fixes problems in 2.0.0-RC1. Read the 2.0.0-RC1 changes for
advice on upgrading from 1.x to 2.x.
* Fix: Don't leak connections! There was a regression in 2.0.0-RC1 where
connections were neither closed nor pooled.
* Fix: Revert builder-style return types from OkHttpClient's timeout methods
for binary compatibility with OkHttp 1.x.
* Fix: Don't skip client stream 1 on SPDY/3.1. This fixes SPDY connectivity to
`https://google.com`, which doesn't follow the SPDY/3.1 spec!
* Fix: Always configure NPN headers. This fixes connectivity to
`https://facebook.com` when SPDY and HTTP/2 are both disabled. Otherwise an
unexpected NPN response is received and OkHttp crashes.
* Fix: Write continuation frames when HPACK data is larger than 16383 bytes.
* Fix: Don't drop uncaught exceptions thrown in async calls.
* Fix: Throw an exception eagerly when a request body is not legal. Previously
we ignored the problem at request-building time, only to crash later with a
`NullPointerException`.
* Fix: Include a backwards-compatible `OkHttp-Response-Source` header with
`OkUrlFactory `responses.
* Fix: Don't include a default User-Agent header in requests made with the Call
API. Requests made with OkUrlFactory will continue to have a default user
agent.
* New: Guava-like API to create headers:
```java
Headers headers = Headers.of(name1, value1, name2, value2, ...).
```
* New: Make the content-type header optional for request bodies.
* New: `Response.isSuccessful()` is a convenient API to check response codes.
* New: The response body can now be read outside of the callback. Response
bodies must always be closed, otherwise they will leak connections!
* New: APIs to create multipart request bodies (`MultipartBuilder`) and form
encoding bodies (`FormEncodingBuilder`).
## Version 2.0.0-RC1
_2014-05-23_
OkHttp 2 is designed around a new API that is true to HTTP, with classes for
requests, responses, headers, and calls. It uses modern Java patterns like
immutability and chained builders. The API now offers asynchronous callbacks
in addition to synchronous blocking calls.
#### API Changes
* **New Request and Response types,** each with their own builder. There's also
a `RequestBody` class to write the request body to the network and a
`ResponseBody` to read the response body from the network. The standalone
`Headers` class offers full access to the HTTP headers.
* **Okio dependency added.** OkHttp now depends on
[Okio](https://github.com/square/okio), an I/O library that makes it easier
to access, store and process data. Using this library internally makes OkHttp
faster while consuming less memory. You can write a `RequestBody` as an Okio
`BufferedSink` and a `ResponseBody` as an Okio `BufferedSource`. Standard
`InputStream` and `OutputStream` access is also available.
* **New Call and Callback types** execute requests and receive their
responses. Both types of calls can be canceled via the `Call` or the
`OkHttpClient`.
* **URLConnection support has moved to the okhttp-urlconnection module.**
If you're upgrading from 1.x, this change will impact you. You will need to
add the `okhttp-urlconnection` module to your project and use the
`OkUrlFactory` to create new instances of `HttpURLConnection`:
```java
// OkHttp 1.x:
HttpURLConnection connection = client.open(url);
// OkHttp 2.x:
HttpURLConnection connection = new OkUrlFactory(client).open(url);
```
* **Custom caches are no longer supported.** In OkHttp 1.x it was possible to
define your own response cache with the `java.net.ResponseCache` and OkHttp's
`OkResponseCache` interfaces. Both of these APIs have been dropped. In
OkHttp 2 the built-in disk cache is the only supported response cache.
* **HttpResponseCache has been renamed to Cache.** Install it with
`OkHttpClient.setCache(...)` instead of `OkHttpClient.setResponseCache(...)`.
* **OkAuthenticator has been replaced with Authenticator.** This new
authenticator has access to the full incoming response and can respond with
whichever followup request is appropriate. The `Challenge` class is now a
top-level class and `Credential` is replaced with a utility class called
`Credentials`.
* **OkHttpClient.getFollowProtocolRedirects() renamed to
getFollowSslRedirects()**. We reserve the word _protocol_ for the HTTP
version being used (HTTP/1.1, HTTP/2). The old name of this method was
misleading; it was always used to configure redirects between `https://` and
`http://` schemes.
* **RouteDatabase is no longer public API.** OkHttp continues to track which
routes have failed but this is no exposed in the API.
* **ResponseSource is gone.** This enum exposed whether a response came from
the cache, network, or both. OkHttp 2 offers more detail with raw access to
the cache and network responses in the new `Response` class.
* **TunnelRequest is gone.** It specified how to connect to an HTTP proxy.
OkHttp 2 uses the new `Request` class for this.
* **Dispatcher** is a new class that manages the queue of asynchronous calls. It
implements limits on total in-flight calls and in-flight calls per host.
#### Implementation changes
* Support Android `TrafficStats` socket tagging.
* Drop authentication headers on redirect.
* Added support for compressed data frames.
* Process push promise callbacks in order.
* Update to http/2 draft 12.
* Update to HPACK draft 07.
* Add ALPN support. Maven will use ALPN on OpenJDK 8.
* Update NPN dependency to target `jdk7u60-b13` and `Oracle jdk7u55-b13`.
* Ensure SPDY variants support zero-length DELETE and POST.
* Prevent leaking a cache item's InputStreams when metadata read fails.
* Use a string to identify TLS versions in routes.
* Add frame logger for HTTP/2.
* Replacing `httpMinorVersion` with `Protocol`. Expose HTTP/1.0 as a potential protocol.
* Use `Protocol` to describe framing.
* Implement write timeouts for HTTP/1.1 streams.
* Avoid use of SPDY stream ID 1, as that's typically used for UPGRADE.
* Support OAuth in `Authenticator`.
* Permit a dangling semicolon in media type parsing.
## Version 1.x
[Change log](changelog_1x.md)
[brick]: https://noncombatant.org/2015/05/01/about-http-public-key-pinning/
[interceptors]: https://square.github.io/okhttp/interceptors/
[webdav]: https://tools.ietf.org/html/rfc4918
================================================
FILE: docs/changelogs/changelog_3x.md
================================================
OkHttp 3.x Change Log
=====================
## Version 3.14.9
_2020-05-17_
* Fix: Don't crash when running as a plugin in Android Studio Canary 4.1. To enable
platform-specific TLS features OkHttp must detect whether it's running in a JVM or in Android.
The upcoming Android Studio runs in a JVM but has classes from Android and that confused OkHttp!
## Version 3.14.8
_2020-04-28_
* Fix: Don't crash on Java 8u252 which introduces an API previously found only on Java 9 and
above. See [Jetty's overview][jetty_8_252] of the API change and its consequences.
## Version 3.14.7
_2020-02-24_
* Fix: Don't crash on Android 11 due to use of restricted methods. This prevents a crash with the
exception, "Expected Android API level 21+ but was 29".
## Version 3.14.6
_2020-01-11_
* Fix: Don't crash if the connection is closed when sending a degraded ping. This fixes a
regression that was introduced in OkHttp 3.14.5.
## Version 3.14.5
_2020-01-03_
* Fix: Degrade HTTP/2 connections after a timeout. When an HTTP/2 stream times out it may impact
the stream only or the entire connection. With this fix OkHttp will now send HTTP/2 pings after
a stream timeout to determine whether the connection should remain eligible for pooling.
## Version 3.14.4
_2019-09-29_
* Fix: Cancel calls that fail due to unexpected exceptions. We had a bug where an enqueued call
would never call back if it crashed with an unchecked throwable, such as a
`NullPointerException` or `OutOfMemoryError`. We now call `Callback.onFailure()` with an
`IOException` that reports the call as canceled. The triggering exception is still delivered to
the thread's `UncaughtExceptionHandler`.
* Fix: Don't evict incomplete entries when iterating the cache. We had a bug where iterating
`Cache.urls()` would prevent in-flight entries from being written.
## Version 3.14.3
_2019-09-10_
* Fix: Don't lose HTTP/2 flow control bytes when incoming data races with a stream close. If this
happened enough then eventually the connection would stall.
* Fix: Acknowledge and apply inbound HTTP/2 settings atomically. Previously we had a race where we
could use new flow control capacity before acknowledging it, causing strict HTTP/2 servers to
fail the call.
* Fix: Recover gracefully when a coalesced connection immediately goes unhealthy.
## Version 3.14.2
_2019-05-19_
* Fix: Lock in a route when recovering from an HTTP/2 connection error. We had a bug where two
calls that failed at the same time could cause OkHttp to crash with a `NoSuchElementException`
instead of the expected `IOException`.
* Fix: Don't crash with a `NullPointerException` when formatting an error message describing a
truncated response from an HTTPS proxy.
## Version 3.14.1
_2019-04-10_
* Fix: Don't crash when an interceptor retries when there are no more routes. This was an
edge-case regression introduced with the events cleanup in 3.14.0.
* Fix: Provide actionable advice when the exchange is non-null. Prior to 3.14, OkHttp would
silently leak connections when an interceptor retries without closing the response body. With
3.14 we detect this problem but the exception was not helpful.
## Version 3.14.0
_2019-03-14_
* **This release deletes the long-deprecated `OkUrlFactory` and `OkApacheClient` APIs.** These
facades hide OkHttp's implementation behind another client's API. If you still need this please
copy and paste [ObsoleteUrlFactory.java][obsolete_url_factory] or
[ObsoleteApacheClient.java][obsolete_apache_client] into your project.
* **OkHttp now supports duplex calls over HTTP/2.** With normal HTTP calls the request must finish
before the response starts. With duplex, request and response bodies are transmitted
simultaneously. This can be used to implement interactive conversations within a single HTTP
call.
Create duplex calls by overriding the new `RequestBody.isDuplex()` method to return true.
This simple option dramatically changes the behavior of the request body and of the entire
call.
The `RequestBody.writeTo()` method may now retain a reference to the provided sink and
hand it off to another thread to write to it after `writeTo` returns.
The `EventListener` may now see requests and responses interleaved in ways not previously
permitted. For example, a listener may receive `responseHeadersStart()` followed by
`requestBodyEnd()`, both on the same call. Such events may be triggered by different threads
even for a single call.
Interceptors that rewrite or replace the request body may now inadvertently interfere with
duplex request bodies. Such interceptors should check `RequestBody.isDuplex()` and avoid
accessing the request body when it is.
Duplex calls require HTTP/2. If HTTP/1 is established instead the duplex call will fail. The
most common use of duplex calls is [gRPC][grpc_http2].
* New: Prevent OkHttp from retransmitting a request body by overriding `RequestBody.isOneShot()`.
This is most useful when writing the request body is destructive.
* New: We've added `requestFailed()` and `responseFailed()` methods to `EventListener`. These
are called instead of `requestBodyEnd()` and `responseBodyEnd()` in some failure situations.
They may also be fired in cases where no event was published previously. In this release we did
an internal rewrite of our event code to fix problems where events were lost or unbalanced.
* Fix: Don't leak a connection when a call is canceled immediately preceding the `onFailure()`
callback.
* Fix: Apply call timeouts when connecting duplex calls, web sockets, and server-sent events.
Once the streams are established no further timeout is enforced.
* Fix: Retain the `Route` when a connection is reused on a redirect or other follow-up. This was
causing some `Authenticator` calls to see a null route when non-null was expected.
* Fix: Use the correct key size in the name of `TLS_AES_128_CCM_8_SHA256` which is a TLS 1.3
cipher suite. We accidentally specified a key size of 256, preventing that cipher suite from
being selected for any TLS handshakes. We didn't notice because this cipher suite isn't
supported on Android, Java, or Conscrypt.
We removed this cipher suite and `TLS_AES_128_CCM_SHA256` from the restricted, modern, and
compatible sets of cipher suites. These two cipher suites aren't enabled by default in either
Firefox or Chrome.
See our [TLS Configuration History][tls_configuration_history] tracker for a log of all changes
to OkHttp's default TLS options.
* New: Upgrade to Conscrypt 2.0.0. OkHttp works with other versions of Conscrypt but this is the
version we're testing against.
```kotlin
implementation("org.conscrypt:conscrypt-openjdk-uber:2.0.0")
```
* New: Update the embedded public suffixes list.
## Version 3.13.1
_2019-02-05_
* Fix: Don't crash when using a custom `X509TrustManager` or `SSLSocket` on Android. When we
removed obsolete code for Android 4.4 we inadvertently also removed support for custom
subclasses. We've restored that support!
## Version 3.13.0
_2019-02-04_
* **This release bumps our minimum requirements to Java 8+ or Android 5+.** Cutting off old
devices is a serious change and we don't do it lightly! [This post][require_android_5] explains
why we're doing this and how to upgrade.
The OkHttp 3.12.x branch will be our long-term branch for Android 2.3+ (API level 9+) and Java
7+. These platforms lack support for TLS 1.2 and should not be used. But because upgrading is
difficult we will backport critical fixes to the 3.12.x branch through December 31, 2021. (This
commitment was originally through December 31, 2020; we have since extended it.)
* **TLSv1 and TLSv1.1 are no longer enabled by default.** Major web browsers are working towards
removing these versions altogether in early 2020. If your servers aren't ready yet you can
configure OkHttp 3.13 to allow TLSv1 and TLSv1.1 connections:
```
OkHttpClient client = new OkHttpClient.Builder()
.connectionSpecs(Arrays.asList(ConnectionSpec.COMPATIBLE_TLS))
.build();
```
* New: You can now access HTTP trailers with `Response.trailers()`. This method may only be called
after the entire HTTP response body has been read.
* New: Upgrade to Okio 1.17.3. If you're on Kotlin-friendly Okio 2.x this release requires 2.2.2
or newer.
```kotlin
implementation("com.squareup.okio:okio:1.17.3")
```
* Fix: Don't miss cancels when sending HTTP/2 request headers.
* Fix: Don't miss whole operation timeouts when calls redirect.
* Fix: Don't leak connections if web sockets have malformed responses or if `onOpen()` throws.
* Fix: Don't retry when request bodies fail due to `FileNotFoundException`.
* Fix: Don't crash when URLs have IPv4-mapped IPv6 addresses.
* Fix: Don't crash when building `HandshakeCertificates` on Android API 28.
* Fix: Permit multipart file names to contain non-ASCII characters.
* New: API to get MockWebServer's dispatcher.
* New: API to access headers as `java.time.Instant`.
* New: Fail fast if a `SSLSocketFactory` is used as a `SocketFactory`.
* New: Log the TLS handshake in `LoggingEventListener`.
## Version 3.12.13
_2021-01-30_
* Fix: Work around a crash in Android 10 and 11 that may be triggered when two threads
concurrently close an SSL socket. This would have appeared in crash logs as
`NullPointerException: bio == null`.
## Version 3.12.12
_2020-05-17_
* Fix: Don't crash when running as a plugin in Android Studio Canary 4.1. To enable
platform-specific TLS features OkHttp must detect whether it's running in a JVM or in Android.
The upcoming Android Studio runs in a JVM but has classes from Android and that confused OkHttp!
## Version 3.12.11
_2020-04-28_
* Fix: Don't crash on Java 8u252 which introduces an API previously found only on Java 9 and
above. See [Jetty's overview][jetty_8_252] of the API change and its consequences.
## Version 3.12.10
_2020-02-29_
* Fix: Don't crash on Android 4.1 when detecting methods that became restricted in Android 11.
Supporting a full decade of Android releases on our 3.12.x branch is tricky!
## Version 3.12.9
_2020-02-24_
* Fix: Don't crash on Android 11 due to use of restricted methods. This prevents a crash with the
exception, "Expected Android API level 21+ but was 29".
## Version 3.12.8
_2020-01-11_
* Fix: Don't crash if the connection is closed when sending a degraded ping. This fixes a
regression that was introduced in OkHttp 3.12.7.
## Version 3.12.7
_2020-01-03_
* Fix: Degrade HTTP/2 connections after a timeout. When an HTTP/2 stream times out it may impact
the stream only or the entire connection. With this fix OkHttp will now send HTTP/2 pings after
a stream timeout to determine whether the connection should remain eligible for pooling.
## Version 3.12.6
_2019-09-29_
* Fix: Cancel calls that fail due to unexpected exceptions. We had a bug where an enqueued call
would never call back if it crashed with an unchecked throwable, such as a
`NullPointerException` or `OutOfMemoryError`. We now call `Callback.onFailure()` with an
`IOException` that reports the call as canceled. The triggering exception is still delivered to
the thread's `UncaughtExceptionHandler`.
* Fix: Don't evict incomplete entries when iterating the cache. We had a bug where iterating
`Cache.urls()` would prevent in-flight entries from being written.
## Version 3.12.5
_2019-09-10_
* Fix: Don't lose HTTP/2 flow control bytes when incoming data races with a stream close. If this
happened enough then eventually the connection would stall.
* Fix: Acknowledge and apply inbound HTTP/2 settings atomically. Previously we had a race where we
could use new flow control capacity before acknowledging it, causing strict HTTP/2 servers to
fail the call.
## Version 3.12.4
_2019-09-04_
* Fix: Don't crash looking up an absent class on certain buggy Android 4.x devices.
## Version 3.12.3
_2019-05-07_
* Fix: Permit multipart file names to contain non-ASCII characters.
* Fix: Retain the `Route` when a connection is reused on a redirect or other follow-up. This was
causing some `Authenticator` calls to see a null route when non-null was expected.
## Version 3.12.2
_2019-03-14_
* Fix: Don't crash if the HTTPS server returns no certificates in the TLS handshake.
* Fix: Don't leak a connection when a call is canceled immediately preceding the `onFailure()`
callback.
## Version 3.12.1
_2018-12-23_
* Fix: Remove overlapping `package-info.java`. This caused issues with some build tools.
## Version 3.12.0
_2018-11-16_
* **OkHttp now supports TLS 1.3.** This requires either Conscrypt or Java 11+.
* **Proxy authenticators are now asked for preemptive authentication.** OkHttp will now request
authentication credentials before creating TLS tunnels through HTTP proxies (HTTP `CONNECT`).
Authenticators should identify preemptive authentications by the presence of a challenge whose
scheme is "OkHttp-Preemptive".
* **OkHttp now offers full-operation timeouts.** This sets a limit on how long the entire call may
take and covers resolving DNS, connecting, writing the request body, server processing, and
reading the full response body. If a call requires redirects or retries all must complete within
one timeout period.
Use `OkHttpClient.Builder.callTimeout()` to specify the default duration and `Call.timeout()` to
specify the timeout of an individual call.
* New: Return values and fields are now non-null unless otherwise annotated.
* New: `LoggingEventListener` makes it easy to get basic visibility into a call's performance.
This class is in the `logging-interceptor` artifact.
* New: `Headers.Builder.addUnsafeNonAscii()` allows non-ASCII values to be added without an
immediate exception.
* New: Headers can be redacted in `HttpLoggingInterceptor`.
* New: `Headers.Builder` now accepts dates.
* New: OkHttp now accepts `java.time.Duration` for timeouts on Java 8+ and Android 26+.
* New: `Challenge` includes all authentication parameters.
* New: Upgrade to BouncyCastle 1.60, Conscrypt 1.4.0, and Okio 1.15.0. We don't yet require
Kotlin-friendly Okio 2.x but OkHttp works fine with that series.
```kotlin
implementation("org.bouncycastle:bcprov-jdk15on:1.60")
implementation("org.conscrypt:conscrypt-openjdk-uber:1.4.0")
implementation("com.squareup.okio:okio:1.15.0")
```
* Fix: Handle dispatcher executor shutdowns gracefully. When there aren't any threads to carry a
call its callback now gets a `RejectedExecutionException`.
* Fix: Don't permanently cache responses with `Cache-Control: immutable`. We misunderstood the
original `immutable` proposal!
* Fix: Change `Authenticator`'s `Route` parameter to be nullable. This was marked as non-null but
could be called with null in some cases.
* Fix: Don't create malformed URLs when `MockWebServer` is reached via an IPv6 address.
* Fix: Don't crash if the system default authenticator is null.
* Fix: Don't crash generating elliptic curve certificates on Android.
* Fix: Don't crash doing platform detection on RoboVM.
* Fix: Don't leak socket connections when web socket upgrades fail.
## Version 3.11.0
_2018-07-12_
* **OkHttp's new okhttp-tls submodule tames HTTPS and TLS.**
`HeldCertificate` is a TLS certificate and its private key. Generate a certificate with its
builder then use it to sign another certificate or perform a TLS handshake. The
`certificatePem()` method encodes the certificate in the familiar PEM format
(`--- BEGIN CERTIFICATE ---`); the `privateKeyPkcs8Pem()` does likewise for the private key.
`HandshakeCertificates` holds the TLS certificates required for a TLS handshake. On the server
it keeps your `HeldCertificate` and its chain. On the client it keeps the root certificates
that are trusted to sign a server's certificate chain. `HandshakeCertificates` also works with
mutual TLS where these roles are reversed.
These classes make it possible to enable HTTPS in MockWebServer in [just a few lines of
code][https_server_sample].
* **OkHttp now supports prior knowledge cleartext HTTP/2.** Enable this by setting
`Protocol.H2_PRIOR_KNOWLEDGE` as the lone protocol on an `OkHttpClient.Builder`. This mode
only supports `http:` URLs and is best suited in closed environments where HTTPS is
inappropriate.
* New: `HttpUrl.get(String)` is an alternative to `HttpUrl.parse(String)` that throws an exception
when the URL is malformed instead of returning null. Use this to avoid checking for null in
situations where the input is known to be well-formed. We've also added `MediaType.get(String)`
which is an exception-throwing alternative to `MediaType.parse(String)`.
* New: The `EventListener` API previewed in OkHttp 3.9 has graduated to a stable API. Use this
interface to track metrics and monitor HTTP requests' size and duration.
* New: `okhttp-dnsoverhttps` is an experimental API for doing DNS queries over HTTPS. Using HTTPS
for DNS offers better security and potentially better performance. This feature is a preview:
the API is subject to change.
* New: `okhttp-sse` is an early preview of Server-Sent Events (SSE). This feature is incomplete
and is only suitable for experimental use.
* New: MockWebServer now supports client authentication (mutual TLS). Call `requestClientAuth()`
to permit an optional client certificate or `requireClientAuth()` to require one.
* New: `RecordedRequest.getHandshake()` returns the HTTPS handshake of a request sent to
`MockWebServer`.
* Fix: Honor the `MockResponse` header delay in MockWebServer.
* Fix: Don't release HTTP/2 connections that have multiple canceled calls. We had a bug where
canceling calls would cause the shared HTTP/2 connection to be unnecessarily released. This
harmed connection reuse.
* Fix: Ensure canceled and discarded HTTP/2 data is not permanently counted against the limited
flow control window. We had a few bugs where window size accounting was broken when streams
were canceled or reset.
* Fix: Recover gracefully if the TLS session returns an unexpected version (`NONE`) or cipher
suite (`SSL_NULL_WITH_NULL_NULL`).
* Fix: Don't change Conscrypt configuration globally. We migrated from a process-wide setting to
configuring only OkHttp's TLS sockets.
* Fix: Prefer TLSv1.2 where it is available. On certain older platforms it is necessary to opt-in
to TLSv1.2.
* New: `Request.tag()` permits multiple tags. Use a `Class>` as a key to identify tags. Note
that `tag()` now returns null if the request has no tag. Previously this would return the
request itself.
* New: `Headers.Builder.addAll(Headers)`.
* New: `ResponseBody.create(MediaType, ByteString)`.
* New: Embed R8/ProGuard rules in the jar. These will be applied automatically by R8.
* Fix: Release the connection if `Authenticator` throws an exception.
* Fix: Change the declaration of `OkHttpClient.cache()` to return a `@Nullable Cache`. The return
value has always been nullable but it wasn't declared properly.
* Fix: Reverse suppression of connect exceptions. When both a call and its retry fail, we now
throw the initial exception which is most likely to be actionable.
* Fix: Retain interrupted state when throwing `InterruptedIOException`. A single interrupt should
now be sufficient to break out an in-flight OkHttp call.
* Fix: Don't drop a call to `EventListener.callEnd()` when the response body is consumed inside an
interceptor.
## Version 3.10.0
_2018-02-24_
* **The pingInterval() feature now aggressively checks connectivity for web
sockets and HTTP/2 connections.**
Previously if you configured a ping interval that would cause OkHttp to send
pings, but it did not track whether the reply pongs were received. With this
update OkHttp requires that every ping receive a response: if it does not
the connection will be closed and the listener's `onFailure()` method will
be called.
Web sockets have always been had pings, but pings on HTTP/2 connections is
new in this release. Pings are used for connections that are busy carrying
calls and for idle connections in the connection pool. (Pings do not impact
when pooled connections are evicted).
If you have a configured ping interval, you should confirm that it is long
enough for a roundtrip from client to server. If your ping interval is too
short, slow connections may be misinterpreted as failed connections. A ping
interval of 30 seconds is reasonable for most use cases.
* **OkHttp now supports [Conscrypt][conscrypt].** Conscrypt is a Java Security
Provider that integrates BoringSSL into the Java platform. Conscrypt
supports more cipher suites than the JVM’s default provider and may also
execute more efficiently.
To use it, first register a [Conscrypt dependency][conscrypt_dependency] in
your build system.
OkHttp will use Conscrypt if you set the `okhttp.platform` system property
to `conscrypt`.
Alternatively, OkHttp will also use Conscrypt if you install it as your
preferred security provider. To do so, add the following code to execute
before you create your `OkHttpClient`.
```
Security.insertProviderAt(
new org.conscrypt.OpenSSLProvider(), 1);
```
Conscrypt is the bundled security provider on Android so it is not necessary
to configure it on that platform.
* New: `HttpUrl.addQueryParameter()` percent-escapes more characters.
Previously several ASCII punctuation characters were not percent-escaped
when used with this method. This does not impact already-encoded query
parameters in APIs like `HttpUrl.parse()` and
`HttpUrl.Builder.addEncodedQueryParameter()`.
* New: CBC-mode ECDSA cipher suites have been removed from OkHttp's default
configuration: `TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA` and
`TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`. This tracks a [Chromium
change][remove_cbc_ecdsa] to remove these cipher suites because they are
fragile and rarely-used.
* New: Don't fall back to common name (CN) verification for hostnames. This
behavior was deprecated with RFC 2818 in May 2000 and was recently dropped
from major web browsers.
* New: Honor the `Retry-After` response header. HTTP 503 (Unavailable)
responses are retried automatically if this header is present and its delay
is 0 seconds. HTTP 408 (Client Timeout) responses are retried automatically
if the header is absent or its delay is 0 seconds.
* New: Allow request bodies for all HTTP methods except GET and HEAD.
* New: Automatic module name of `okhttp3` for use with the Java Platform
Module System.
* New: Log gzipped bodies when `HttpLoggingInterceptor` is used as a network
interceptor.
* New: `Protocol.QUIC` constant. This protocol is not supported but this
constant is included for completeness.
* New: Upgrade to Okio 1.14.0.
```xml
com.squareup.okiookio1.14.0
com.squareup.okio:okio:1.14.0
```
* Fix: Handle `HTTP/1.1 100 Continue` status lines, even on requests that did
not send the `Expect: continue` request header.
* Fix: Do not count web sockets toward the dispatcher's per-host connection
limit.
* Fix: Avoid using invalid HTTPS sessions. This prevents OkHttp from crashing
with the error, `Unexpected TLS version: NONE`.
* Fix: Don't corrupt the response cache when a 304 (Not Modified) response
overrides the stored "Content-Encoding" header.
* Fix: Gracefully shut down the HTTP/2 connection before it exhausts the
namespace of stream IDs (~536 million streams).
* Fix: Never pass a null `Route` to `Authenticator`. There was a bug where
routes were omitted for eagerly-closed connections.
## Version 3.9.1
_2017-11-18_
* New: Recover gracefully when Android's DNS crashes with an unexpected
`NullPointerException`.
* New: Recover gracefully when Android's socket connections crash with an
unexpected `ClassCastException`.
* Fix: Don't include the URL's fragment in `encodedQuery()` when the query
itself is empty.
## Version 3.9.0
_2017-09-03_
* **Interceptors are more capable.** The `Chain` interface now offers access
to the call and can adjust all call timeouts. Note that this change is
source-incompatible for code that implements the `Chain` interface.
We don't expect this to be a problem in practice!
* **OkHttp has an experimental new API for tracking metrics.** The new
`EventListener` API is designed to help developers monitor HTTP requests'
size and duration. This feature is an unstable preview: the API is subject
to change, and the implementation is incomplete. This is a big new API we
are eager for feedback.
* New: Support ALPN via Google Play Services' Dynamic Security Provider. This
expands HTTP/2 support to older Android devices that have Google Play
Services.
* New: Consider all routes when looking for candidate coalesced connections.
This increases the likelihood that HTTP/2 connections will be shared.
* New: Authentication challenges and credentials now use a charset. Use this in
your authenticator to support user names and passwords with non-ASCII
characters.
* New: Accept a charset in `FormBody.Builder`. Previously form bodies were
always UTF-8.
* New: Support the `immutable` cache-control directive.
* Fix: Don't crash when an HTTP/2 call is redirected while the connection is
being shut down.
* Fix: Don't drop headers of healthy streams that raced with `GOAWAY` frames.
This bug would cause HTTP/2 streams to occasional hang when the connection
was shutting down.
* Fix: Honor `OkHttpClient.retryOnConnectionFailure()` when the response is a
HTTP 408 Request Timeout. If retries are enabled, OkHttp will retry exactly
once in response to a 408.
* Fix: Don't crash when reading the empty `HEAD` response body if it specifies
a `Content-Length`.
* Fix: Don't crash if the thread is interrupted while reading the public
suffix database.
* Fix: Use relative resource path when loading the public suffix database.
Loading the resource using a path relative to the class prevents conflicts
when the OkHttp classes are relocated (shaded) by allowing multiple private
copies of the database.
* Fix: Accept cookies for URLs that have an IPv6 address for a host.
* Fix: Don't log the protocol (HTTP/1.1, h2) in HttpLoggingInterceptor if the
protocol isn't negotiated yet! Previously we'd log HTTP/1.1 by default, and
this was confusing.
* Fix: Omit the message from MockWebServer's HTTP/2 `:status` header.
* Fix: Handle 'Expect: 100 Continue' properly in MockWebServer.
## Version 3.8.1
_2017-06-18_
* Fix: Recover gracefully from stale coalesced connections. We had a bug where
connection coalescing (introduced in OkHttp 3.7.0) and stale connection
recovery could interact to cause a `NoSuchElementException` crash in the
`RouteSelector`.
## Version 3.8.0
_2017-05-13_
* **OkHttp now uses `@Nullable` to annotate all possibly-null values.** We've
added a compile-time dependency on the JSR 305 annotations. This is a
[provided][maven_provided] dependency and does not need to be included in
your build configuration, `.jar` file, or `.apk`. We use
`@ParametersAreNonnullByDefault` and all parameters and return types are
never null unless explicitly annotated `@Nullable`.
* **Warning: this release is source-incompatible for Kotlin users.**
Nullability was previously ambiguous and lenient but now the compiler will
enforce strict null checks.
* New: The response message is now non-null. This is the "Not Found" in the
status line "HTTP 404 Not Found". If you are building responses
programmatically (with `new Response.Builder()`) you must now always supply
a message. An empty string `""` is permitted. This value was never null on
responses returned by OkHttp itself, and it was an old mistake to permit
application code to omit a message.
* The challenge's scheme and realm are now non-null. If you are calling
`new Challenge(scheme, realm)` you must provide non-null values. These were
never null in challenges created by OkHttp, but could have been null in
application code that creates challenges.
* New: The `TlsVersion` of a `Handshake` is now non-null. If you are calling
`Handshake.get()` with a null TLS version, you must instead now provide a
non-null `TlsVersion`. Cache responses persisted prior to OkHttp 3.0 did not
store a TLS version; for these unknown values the handshake is defaulted to
`TlsVersion.SSL_3_0`.
* New: Upgrade to Okio 1.13.0.
```xml
com.squareup.okiookio1.13.0
com.squareup.okio:okio:1.13.0
```
* Fix: gracefully recover when Android 7.0's sockets throw an unexpected
`NullPointerException`.
## Version 3.7.0
_2017-04-15_
* **OkHttp no longer recovers from TLS handshake failures by attempting a TLSv1 connection.**
The fallback was necessary for servers that implemented version negotiation incorrectly. Now
that 99.99% of servers do it right this fallback is obsolete.
* Fix: Do not honor cookies set on a public domain. Previously a malicious site could inject
cookies on top-level domains like `co.uk` because our cookie parser didn't honor the [public
suffix][public_suffix] list. Alongside this fix is a new API, `HttpUrl.topPrivateDomain()`,
which returns the privately domain name if the URL has one.
* Fix: Change `MediaType.charset()` to return null for unexpected charsets.
* Fix: Don't skip cache invalidation if the invalidating response has no body.
* Fix: Don't use a cryptographic random number generator for web sockets. Some Android devices
implement `SecureRandom` incorrectly!
* Fix: Correctly canonicalize IPv6 addresses in `HttpUrl`. This prevented OkHttp from trusting
HTTPS certificates issued to certain IPv6 addresses.
* Fix: Don't reuse connections after an unsuccessful `Expect: 100-continue`.
* Fix: Handle either `TLS_` or `SSL_` prefixes for cipher suite names. This is necessary for
IBM JVMs that use the `SSL_` prefix exclusively.
* Fix: Reject HTTP/2 data frames if the stream ID is 0.
* New: Upgrade to Okio 1.12.0.
```xml
com.squareup.okiookio1.12.0
com.squareup.okio:okio:1.12.0
```
* New: Connection coalescing. OkHttp may reuse HTTP/2 connections across calls that share an IP
address and HTTPS certificate, even if their domain names are different.
* New: MockWebServer's `RecordedRequest` exposes the requested `HttpUrl` with `getRequestUrl()`.
## Version 3.6.0
_2017-01-29_
* Fix: Don't crash with a "cache is closed" error when there is an error initializing the cache.
* Fix: Calling `disconnect()` on a connecting `HttpUrlConnection` could cause it to retry in an
infinite loop! This regression was introduced in OkHttp 2.7.0.
* Fix: Drop cookies that contain ASCII NULL and other bad characters. Previously such cookies
would cause OkHttp to crash when they were included in a request.
* Fix: Release duplicated multiplexed connections. If we concurrently establish connections to an
HTTP/2 server, close all but the first connection.
* Fix: Fail the HTTP/2 connection if first frame isn't `SETTINGS`.
* Fix: Forbid spaces in header names.
* Fix: Don't offer to do gzip if the request is partial.
* Fix: MockWebServer is now usable with JUnit 5. That update [broke the rules][junit_5_rules].
* New: Support `Expect: 100-continue` as a request header. Callers can use this header to
pessimistically hold off on transmitting a request body until a server gives the go-ahead.
* New: Permit network interceptors to rewrite the host header for HTTP/2. This makes it possible
to do domain fronting.
* New: charset support for `Credentials.basic()`.
## Version 3.5.0
_2016-11-30_
* **Web Sockets are now a stable feature of OkHttp.** Since being introduced as a beta feature in
OkHttp 2.3 our web socket client has matured. Connect to a server's web socket with
`OkHttpClient.newWebSocket()`, send messages with `send()`, and receive messages with the
`WebSocketListener`.
The `okhttp-ws` submodule is no longer available and `okhttp-ws` artifacts from previous
releases of OkHttp are not compatible with OkHttp 3.5. When upgrading to the new package
please note that the `WebSocket` and `WebSocketCall` classes have been merged. Sending messages
is now asynchronous and they may be enqueued before the web socket is connected.
* **OkHttp no longer attempts a direct connection if the system's HTTP proxy fails.** This
behavior was surprising because OkHttp was disregarding the user's specified configuration. If
you need to customize proxy fallback behavior, implement your own `java.net.ProxySelector`.
* Fix: Support TLSv1.3 on devices that support it.
* Fix: Share pooled connections across equivalent `OkHttpClient` instances. Previous releases had
a bug where a shared connection pool did not guarantee shared connections in some cases.
* Fix: Prefer the server's response body on all conditional cache misses. Previously we would
return the cached response's body if it had a newer `Last-Modified` date.
* Fix: Update the stored timestamp on conditional cache hits.
* New: Optimized HTTP/2 request header encoding. More headers are HPACK-encoded and string
literals are now Huffman-encoded.
* New: Expose `Part` headers and body in `Multipart`.
* New: Make `ResponseBody.string()` and `ResponseBody.charStream()` BOM-aware. If your HTTP
response body begins with a [byte order mark][bom] it will be consumed and used to select a
charset for the remaining bytes. Most applications should not need a byte order mark.
* New: Upgrade to Okio 1.11.0.
```xml
com.squareup.okiookio1.11.0
com.squareup.okio:okio:1.11.0
```
* Fix: Avoid sending empty HTTP/2 data frames when there is no request body.
* Fix: Add a leading `.` for better domain matching in `JavaNetCookieJar`.
* Fix: Gracefully recover from HTTP/2 connection shutdowns at start of request.
* Fix: Be lenient if a `MediaType`'s character set is `'single-quoted'`.
* Fix: Allow horizontal tab characters in header values.
* Fix: When parsing HTTP authentication headers permit challenge parameters in any order.
## Version 3.4.2
_2016-11-03_
* Fix: Recover gracefully when an HTTP/2 connection is shutdown. We had a
bug where shutdown HTTP/2 connections were considered usable. This caused
infinite loops when calls attempted to recover.
## Version 3.4.1
_2016-07-10_
* **Fix a major bug in encoding HTTP headers.** In 3.4.0 and 3.4.0-RC1 OkHttp
had an off-by-one bug in our HPACK encoder. This bug could have caused the
wrong headers to be emitted after a sequence of HTTP/2 requests! Everyone
who is using OkHttp 3.4.0 or 3.4.0-RC1 should upgrade for this bug fix.
## Version 3.4.0
_2016-07-08_
* New: Support dynamic table size changes to HPACK Encoder.
* Fix: Use `TreeMap` in `Headers.toMultimap()`. This makes string lookups on
the returned map case-insensitive.
* Fix: Don't share the OkHttpClient's `Dispatcher` in `HttpURLConnection`.
## Version 3.4.0-RC1
_2016-07-02_
* **We’ve rewritten HttpURLConnection and HttpsURLConnection.** Previously we
shared a single HTTP engine between two frontend APIs: `HttpURLConnection`
and `Call`. With this release we’ve rearranged things so that the
`HttpURLConnection` frontend now delegates to the `Call` APIs internally.
This has enabled substantial simplifications and optimizations in the OkHttp
core for both frontends.
For most HTTP requests the consequences of this change will be negligible.
If your application uses `HttpURLConnection.connect()`,
`setFixedLengthStreamingMode()`, or `setChunkedStreamingMode()`, OkHttp will
now use a async dispatcher thread to establish the HTTP connection.
We don’t expect this change to have any behavior or performance
consequences. Regardless, please exercise your `OkUrlFactory` and
`HttpURLConnection` code when applying this update.
* **Cipher suites may now have arbitrary names.** Previously `CipherSuite` was
a Java enum and it was impossible to define new cipher suites without first
upgrading OkHttp. With this change it is now a regular Java class with
enum-like constants. Application code that uses enum methods on cipher
suites (`ordinal()`, `name()`, etc.) will break with this change.
* Fix: `CertificatePinner` now matches canonicalized hostnames. Previously
this was case sensitive. This change should also make it easier to configure
certificate pinning for internationalized domain names.
* Fix: Don’t crash on non-ASCII `ETag` headers. Previously OkHttp would reject
these headers when validating a cached response.
* Fix: Don’t allow remote peer to arbitrarily size the HPACK decoder dynamic
table.
* Fix: Honor per-host configuration in Android’s network security config.
Previously disabling cleartext for any host would disable cleartext for all
hosts. Note that this setting is only available on Android 24+.
* New: HPACK compression is now dynamic. This should improve performance when
transmitting request headers over HTTP/2.
* New: `Dispatcher.setIdleCallback()` can be used to signal when there are no
calls in flight. This is useful for [testing with
Espresso][okhttp_idling_resource].
* New: Upgrade to Okio 1.9.0.
```xml
com.squareup.okiookio1.9.0
```
## Version 3.3.1
_2016-05-28_
* Fix: The plaintext check in HttpLoggingInterceptor incorrectly classified
newline characters as control characters. This is fixed.
* Fix: Don't crash reading non-ASCII characters in HTTP/2 headers or in cached
HTTP headers.
* Fix: Retain the response body when an attempt to open a web socket returns a
non-101 response code.
## Version 3.3.0
_2016-05-24_
* New: `Response.sentRequestAtMillis()` and `receivedResponseAtMillis()`
methods track the system's local time when network calls are made. These
replace the `OkHttp-Sent-Millis` and `OkHttp-Received-Millis` headers that were
present in earlier versions of OkHttp.
* New: Accept user-provided trust managers in `OkHttpClient.Builder`. This
allows OkHttp to satisfy its TLS requirements directly. Otherwise OkHttp
will use reflection to extract the `TrustManager` from the
`SSLSocketFactory`.
* New: Support prerelease Java 9. This gets ALPN from the platform rather than
relying on the alpn-boot bootclasspath override.
* New: `HttpLoggingInterceptor` now logs connection failures.
* New: Upgrade to Okio 1.8.0.
```xml
com.squareup.okiookio1.8.0
```
* Fix: Gracefully recover from a failure to rebuild the cache journal.
* Fix: Don't corrupt cache entries when a cache entry is evicted while it is
being updated.
* Fix: Make logging more consistent throughout OkHttp.
* Fix: Log plaintext bodies only. This uses simple heuristics to differentiate
text from other data.
* Fix: Recover from `REFUSED_STREAM` errors in HTTP/2. This should improve
interoperability with Nginx 1.10.0, which [refuses][nginx_959] streams
created before HTTP/2 settings have been acknowledged.
* Fix: Improve recovery from failed routes.
* Fix: Accommodate tunneling proxies that close the connection after an auth
challenge.
* Fix: Use the proxy authenticator when authenticating HTTP proxies. This
regression was introduced in OkHttp 3.0.
* Fix: Fail fast if network interceptors transform the response body such that
closing it doesn't also close the underlying stream. We had a bug where
OkHttp would attempt to reuse a connection but couldn't because it was still
held by a prior request.
* Fix: Ensure network interceptors always have access to the underlying
connection.
* Fix: Use `X509TrustManagerExtensions` on Android 17+.
* Fix: Unblock waiting dispatchers on MockWebServer shutdown.
## Version 3.2.0
_2016-02-25_
* Fix: Change the certificate pinner to always build full chains. This
prevents a potential crash when using certificate pinning with the Google
Play Services security provider.
* Fix: Make IPv6 request lines consistent with Firefox and Chrome.
* Fix: Recover gracefully when trimming the response cache fails.
* New: Add multiple path segments using a single string in `HttpUrl.Builder`.
* New: Support SHA-256 pins in certificate pinner.
## Version 3.1.2
_2016-02-10_
* Fix: Don’t crash when finding the trust manager on Robolectric. We attempted
to detect the host platform and got confused because Robolectric looks like
Android but isn’t!
* Fix: Change `CertificatePinner` to skip sanitizing the certificate chain
when no certificates were pinned. This avoids an SSL failure in insecure
“trust everyone” configurations, such as when talking to a development
HTTPS server that has a self-signed certificate.
## Version 3.1.1
_2016-02-07_
* Fix: Don't crash when finding the trust manager if the Play Services (GMS)
security provider is installed.
* Fix: The previous release introduced a performance regression on Android,
caused by looking up CA certificates. This is now fixed.
## Version 3.1.0
_2016-02-06_
* New: WebSockets now defer some writes. This should improve performance for
some applications.
* New: Override `equals()` and `hashCode()` in our new cookie class. This
class now defines equality by value rather than by reference.
* New: Handle 408 responses by retrying the request. This allows servers to
direct clients to retry rather than failing permanently.
* New: Expose the framed protocol in `Connection`. Previously this would
return the application-layer protocol (HTTP/1.1 or HTTP/1.0); now it always
returns the wire-layer protocol (HTTP/2, SPDY/3.1, or HTTP/1.1).
* Fix: Permit the trusted CA root to be pinned by `CertificatePinner`.
* Fix: Silently ignore unknown HTTP/2 settings. Previously this would cause
the entire connection to fail.
* Fix: Don’t crash on unexpected charsets in the logging interceptor.
* Fix: `OkHttpClient` is now non-final for the benefit of mocking frameworks.
Mocking sophisticated classes like `OkHttpClient` is fragile and you
shouldn’t do it. But if that’s how you want to live your life we won’t stand
in your way!
## Version 3.0.1
_2016-01-14_
* Rollback OSGi support. This was causing library jars to include more classes
than expected, which interfered with Gradle builds.
## Version 3.0.0
_2016-01-13_
This release commits to a stable 3.0 API. Read the 3.0.0-RC1 changes for advice
on upgrading from 2.x to 3.x.
* **The `Callback` interface now takes a `Call`**. This makes it easier to
check if the call was canceled from within the callback. When migrating
async calls to this new API, `Call` is now the first parameter for both
`onResponse()` and `onFailure()`.
* Fix: handle multiple cookies in `JavaNetCookieJar` on Android.
* Fix: improve the default HTTP message in MockWebServer responses.
* Fix: don't leak file handles when a conditional GET throws.
* Fix: Use charset specified by the request body content type in OkHttp's
logging interceptor.
* Fix: Don't eagerly release pools on cache hits.
* New: Make OkHttp OSGi ready.
* New: Add already-implemented interfaces Closeable and Flushable to the cache.
## Version 3.0.0-RC1
_2016-01-02_
OkHttp 3 is a major release focused on API simplicity and consistency. The API
changes are numerous but most are cosmetic. Applications should be able to
upgrade from the 2.x API to the 3.x API mechanically and without risk.
Because the release includes breaking API changes, we're changing the project's
package name from `com.squareup.okhttp` to `okhttp3`. This should make it
possible for large applications to migrate incrementally. The Maven group ID
is now `com.squareup.okhttp3`. For an explanation of this strategy, see Jake
Wharton's post, [Java Interoperability Policy for Major Version
Updates][major_versions].
This release obsoletes OkHttp 2.x, and all code that uses OkHttp's
`com.squareup.okhttp` package should upgrade to the `okhttp3` package. Libraries
that depend on OkHttp should upgrade quickly to prevent applications from being
stuck on the old version.
* **There is no longer a global singleton connection pool.** In OkHttp 2.x,
all `OkHttpClient` instances shared a common connection pool by default.
In OkHttp 3.x, each new `OkHttpClient` gets its own private connection pool.
Applications should avoid creating many connection pools as doing so
prevents connection reuse. Each connection pool holds its own set of
connections alive so applications that have many pools also risk exhausting
memory!
The best practice in OkHttp 3 is to create a single OkHttpClient instance
and share it throughout the application. Requests that needs a customized
client should call `OkHttpClient.newBuilder()` on that shared instance.
This allows customization without the drawbacks of separate connection
pools.
* **OkHttpClient is now stateless.** In the 2.x API `OkHttpClient` had getters
and setters. Internally each request was forced to make its own complete
snapshot of the `OkHttpClient` instance to defend against racy configuration
changes. In 3.x, `OkHttpClient` is now stateless and has a builder. Note
that this class is not strictly immutable as it has stateful members like
the connection pool and cache.
* **Get and Set prefixes are now avoided.** With ubiquitous builders
throughout OkHttp these accessor prefixes aren't necessary. Previously
OkHttp used _get_ and _set_ prefixes sporadically which make the API
inconsistent and awkward to explore.
* **OkHttpClient now implements the new `Call.Factory` interface.** This
interface will make your code easier to test. When you test code that makes
HTTP requests, you can use this interface to replace the real `OkHttpClient`
with your own mocks or fakes.
The interface will also let you use OkHttp's API with another HTTP client's
implementation. This is useful in sandboxed environments like Google App
Engine.
* **OkHttp now does cookies.** We've replaced `java.net.CookieHandler` with
a new interface, `CookieJar` and added our own `Cookie` model class. This
new cookie follows the latest RFC and supports the same cookie attributes
as modern web browsers.
* **Form and Multipart bodies are now modeled.** We've replaced the opaque
`FormEncodingBuilder` with the more powerful `FormBody` and
`FormBody.Builder` combo. Similarly we've upgraded `MultipartBuilder` into
`MultipartBody`, `MultipartBody.Part`, and `MultipartBody.Builder`.
* **The Apache HTTP client and HttpURLConnection APIs are deprecated.** They
continue to work as they always have, but we're moving everything to the new
OkHttp 3 API. The `okhttp-apache` and `okhttp-urlconnection` modules should
be only be used to accelerate a transition to OkHttp's request/response API.
These deprecated modules will be dropped in an upcoming OkHttp 3.x release.
* **Canceling batches of calls is now the application's responsibility.**
The API to cancel calls by tag has been removed and replaced with a more
general mechanism. The dispatcher now exposes all in-flight calls via its
`runningCalls()` and `queuedCalls()` methods. You can write code that
selects calls by tag, host, or whatever, and invokes `Call.cancel()` on the
ones that are no longer necessary.
* **OkHttp no longer uses the global `java.net.Authenticator` by default.**
We've changed our `Authenticator` interface to authenticate web and proxy
authentication failures through a single method. An adapter for the old
authenticator is available in the `okhttp-urlconnection` module.
* Fix: Don't throw `IOException` on `ResponseBody.contentLength()` or `close()`.
* Fix: Never throw converting an `HttpUrl` to a `java.net.URI`. This changes
the `uri()` method to handle malformed percent-escapes and characters
forbidden by `URI`.
* Fix: When a connect times out, attempt an alternate route. Previously route
selection was less efficient when differentiating failures.
* New: `Response.peekBody()` lets you access the response body without
consuming it. This may be handy for interceptors!
* New: `HttpUrl.newBuilder()` resolves a link to a builder.
* New: Add the TLS version to the `Handshake`.
* New: Drop `Request.uri()` and `Request#urlString()`. Just use
`Request.url().uri()` and `Request.url().toString()`.
* New: Add URL to HTTP response logging.
* New: Make `HttpUrl` the blessed URL method of `Request`.
## Version 2.x
[Change log](changelog_2x.md)
[bom]: https://en.wikipedia.org/wiki/Byte_order_mark
[conscrypt]: https://github.com/google/conscrypt/
[conscrypt_dependency]: https://github.com/google/conscrypt/#download
[grpc_http2]: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
[https_server_sample]: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/HttpsServer.java
[jetty_8_252]: https://webtide.com/jetty-alpn-java-8u252/
[junit_5_rules]: https://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4-rulesupport
[major_versions]: https://jakewharton.com/java-interoperability-policy-for-major-version-updates/
[maven_provided]: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html
[nginx_959]: https://trac.nginx.org/nginx/ticket/959
[obsolete_apache_client]: https://gist.github.com/swankjesse/09721f72039e3a46cf50f94323deb82d
[obsolete_url_factory]: https://gist.github.com/swankjesse/dd91c0a8854e1559b00f5fc9c7bfae70
[okhttp_idling_resource]: https://github.com/JakeWharton/okhttp-idling-resource
[public_suffix]: https://publicsuffix.org/
[remove_cbc_ecdsa]: https://developers.google.com/web/updates/2016/12/chrome-56-deprecations#remove_cbc-mode_ecdsa_ciphers_in_tls
[require_android_5]: https://code.cash.app/okhttp-3-13-requires-android-5
[tls_configuration_history]: https://square.github.io/okhttp/tls_configuration_history/
[upgrading_to_okhttp_4]: https://square.github.io/okhttp/upgrading_to_okhttp_4/
================================================
FILE: docs/changelogs/changelog_4x.md
================================================
OkHttp 4.x Change Log
=====================
## Version 4.12.0
_2023-10-16_
* Fix: Don't hang taking headers for HTTP 103 responses.
* Fix: Recover gracefully when a cache entry's certificate is corrupted.
* Fix: Fail permanently when there's a failure loading the bundled public suffix database.
This is the dataset that powers `HttpUrl.topPrivateDomain()`.
* Fix: Immediately update the connection's flow control window instead of waiting for the
receiving stream to process it.
This change may increase OkHttp's memory use for applications that make many concurrent HTTP
calls and that can receive data faster than they can process it. Previously, OkHttp limited
HTTP/2 to 16 MiB of unacknowledged data per connection. With this fix there is a limit of 16 MiB
of unacknowledged data per stream and no per-connection limit.
* Fix: Don't operate on a connection after it's been returned to the pool. This race occurred
on failed web socket connection attempts.
* Upgrade: [Okio 3.6.0][okio_3_6_0].
* Upgrade: [Kotlin 1.8.21][kotlin_1_8_21].
## Version 4.11.0
_2023-04-22_
* Fix: Don't fail the call when the response code is ‘HTTP 102 Processing’ or
‘HTTP 103 Early Hints’.
* Fix: Read the response even if writing the request fails. This means you'll get a proper HTTP
response even if the server rejects your request body.
* Fix: Use literal IP addresses directly rather than passing them to `DnsOverHttps`.
* Fix: Embed Proguard rules to prevent warnings from tools like DexGuard and R8. These warnings
were triggered by OkHttp’s feature detection for TLS packages like `org.conscrypt`,
`org.bouncycastle`, and `org.openjsse`.
* Upgrade: Explicitly depend on `kotlin-stdlib-jdk8`. This fixes a problem with dependency
locking. That's a potential security vulnerability, tracked as [CVE-2022-24329].
* Upgrade: [publicsuffix.org data][public_suffix]. This powers `HttpUrl.topPrivateDomain()`.
It's also how OkHttp knows which domains can share cookies with one another.
* Upgrade: [Okio 3.2.0][okio_3_2_0].
## Version 4.10.0
_2022-06-12_
* Upgrade: [Kotlin 1.6.20][kotlin_1_6_20].
* Upgrade: [Okio 3.0.0][okio_3_0_0].
* Fix: Recover gracefully when Android's `NativeCrypto` crashes with `"ssl == null"`. This occurs
when OkHttp retrieves ALPN state on a closed connection.
## Version 4.9.3
_2021-11-21_
* Fix: Don't fail HTTP/2 responses if they complete before a `RST_STREAM` is sent.
## Version 4.9.2
_2021-09-30_
* Fix: Don't include potentially-sensitive header values in `Headers.toString()` or exceptions.
This applies to `Authorization`, `Cookie`, `Proxy-Authorization`, and `Set-Cookie` headers.
* Fix: Don't crash with an `InaccessibleObjectException` when running on JDK17+ with strong
encapsulation enabled.
* Fix: Strictly verify hostnames used with OkHttp's `HostnameVerifier`. Programs that make direct
manual calls to `HostnameVerifier` could be defeated if the hostnames they pass in are not
strictly ASCII. This issue is tracked as [CVE-2021-0341].
## Version 4.9.1
_2021-01-30_
* Fix: Work around a crash in Android 10 and 11 that may be triggered when two threads
concurrently close an SSL socket. This would have appeared in crash logs as
`NullPointerException: bio == null`.
## Version 4.9.0
_2020-09-11_
**With this release, `okhttp-tls` no longer depends on Bouncy Castle and doesn't install the
Bouncy Castle security provider.** If you still need it, you can do it yourself:
```
Security.addProvider(BouncyCastleProvider())
```
You will also need to configure this dependency:
```
dependencies {
implementation "org.bouncycastle:bcprov-jdk15on:1.65"
}
```
* Upgrade: [Kotlin 1.4.10][kotlin_1_4_10]. We now use Kotlin 1.4.x [functional
interfaces][fun_interface] for `Authenticator`, `Interceptor`, and others.
* Upgrade: Build with Conscrypt 2.5.1.
## Version 4.8.1
_2020-08-06_
* Fix: Don't crash in `HeldCertificate.Builder` when creating certificates on older versions of
Android, including Android 6. We were using a feature of `SimpleDateFormat` that wasn't
available in those versions!
## Version 4.8.0
_2020-07-11_
* New: Change `HeldCertificate.Builder` to use its own ASN.1 certificate encoder. This is part
of our effort to remove the okhttp-tls module's dependency on Bouncy Castle. We think Bouncy
Castle is great! But it's a large dependency (6.5 MiB) and its security provider feature
impacts VM-wide behavior.
* New: Reduce contention for applications that make a very high number of concurrent requests.
Previously OkHttp used its connection pool as a lock when making changes to connections and
calls. With this change each connection is locked independently.
* Upgrade: [Okio 2.7.0][okio_2_7_0].
```kotlin
implementation("com.squareup.okio:okio:2.7.0")
```
* Fix: Avoid log messages like "Didn't find class org.conscrypt.ConscryptHostnameVerifier" when
detecting the TLS capabilities of the host platform.
* Fix: Don't crash in `HttpUrl.topPrivateDomain()` when the hostname is malformed.
* Fix: Don't attempt Brotli decompression if the response body is empty.
## Version 4.7.2
_2020-05-20_
* Fix: Don't crash inspecting whether the host platform is JVM or Android. With 4.7.0 and 4.7.1 we
had a crash `IllegalArgumentException: Not a Conscrypt trust manager` because we depended on
initialization order of companion objects.
## Version 4.7.1
_2020-05-18_
* Fix: Pass the right arguments in the trust manager created for `addInsecureHost()`. Without the
fix insecure hosts crash with an `IllegalArgumentException` on Android.
## Version 4.7.0
_2020-05-17_
* New: `HandshakeCertificates.Builder.addInsecureHost()` makes it easy to turn off security in
private development environments that only carry test data. Prefer this over creating an
all-trusting `TrustManager` because only hosts on the allowlist are insecure. From
[our DevServer sample][dev_server]:
```kotlin
val clientCertificates = HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.addInsecureHost("localhost")
.build()
val client = OkHttpClient.Builder()
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)
.build()
```
* New: Add `cacheHit`, `cacheMiss`, and `cacheConditionalHit()` events to `EventListener`. Use
these in logs, metrics, and even test cases to confirm your cache headers are configured as
expected.
* New: Constant string `okhttp3.VERSION`. This is a string like "4.5.0-RC1", "4.5.0", or
"4.6.0-SNAPSHOT" indicating the version of OkHttp in the current runtime. Use this to include
the OkHttp version in custom `User-Agent` headers.
* Fix: Don't crash when running as a plugin in Android Studio Canary 4.1. To enable
platform-specific TLS features OkHttp must detect whether it's running in a JVM or in Android.
The upcoming Android Studio runs in a JVM but has classes from Android and that confused OkHttp!
* Fix: Include the header `Accept: text/event-stream` for SSE calls. This header is not added if
the request already contains an `Accept` header.
* Fix: Don't crash with a `NullPointerException` if a server sends a close while we're sending a
ping. OkHttp had a race condition bug.
## Version 4.6.0
_2020-04-28_
* Fix: Follow HTTP 307 and 308 redirects on methods other than GET and POST. We're reluctant to
change OkHttp's behavior in handling common HTTP status codes, but this fix is overdue! The new
behavior is now consistent with [RFC 7231][rfc_7231_647], which is newer than OkHttp itself.
If you want this update with the old behavior use [this interceptor][legacy_interceptor].
* Fix: Don't crash decompressing web sockets messages. We had a bug where we assumed deflated
bytes in would always yield deflated bytes out and this isn't always the case!
* Fix: Reliably update and invalidate the disk cache on windows. As originally designed our
internal `DiskLruCache` assumes an inode-like file system, where it's fine to delete files that
are currently being read or written. On Windows the file system forbids this so we must be more
careful when deleting and renaming files.
* Fix: Don't crash on Java 8u252 which introduces an API previously found only on Java 9 and
above. See [Jetty's overview][jetty_8_252] of the API change and its consequences.
* New: `MultipartReader` is a streaming decoder for [MIME multipart (RFC 2045)][rfc_2045]
messages. It complements `MultipartBody` which is our streaming encoder.
```kotlin
val response: Response = call.execute()
val multipartReader = MultipartReader(response.body!!)
multipartReader.use {
while (true) {
val part = multipartReader.nextPart() ?: break
process(part.headers, part.body)
}
}
```
* New: `MediaType.parameter()` gets a parameter like `boundary` from a media type like
`multipart/mixed; boundary="abc"`.
* New: `Authenticator.JAVA_NET_AUTHENTICATOR` forwards authentication requests to
`java.net.Authenticator`. This obsoletes `JavaNetAuthenticator` in the `okhttp-urlconnection`
module.
* New: `CertificatePinner` now offers an API for inspecting the configured pins.
* Upgrade: [Okio 2.6.0][okio_2_6_0].
```kotlin
implementation("com.squareup.okio:okio:2.6.0")
```
* Upgrade: [publicsuffix.org data][public_suffix]. This powers `HttpUrl.topPrivateDomain()`.
It's also how OkHttp knows which domains can share cookies with one another.
* Upgrade: [Bouncy Castle 1.65][bouncy_castle_releases]. This dependency is required by the
`okhttp-tls` module.
* Upgrade: [Kotlin 1.3.71][kotlin_1_3_71].
## Version 4.5.0
_2020-04-06_
**This release fixes a severe bug where OkHttp incorrectly detected and recovered from unhealthy
connections.** Stale or canceled connections were incorrectly attempted when they shouldn't have
been, leading to rare cases of infinite retries. Please upgrade to this release!
* Fix: don't return stale DNS entries in `DnsOverHttps`. We were caching DNS results indefinitely
rather than the duration specified in the response's cache-control header.
* Fix: Verify certificate IP addresses in canonical form. When a server presents a TLS certificate
containing an IP address we must match that address against the URL's IP address, even when the
two addresses are encoded differently, such as `192.168.1.1` and `0::0:0:FFFF:C0A8:101`. Note
that OkHttp incorrectly rejected valid certificates resulting in a failure to connect; at no
point were invalid certificates accepted.
* New: `OkHttpClient.Builder.minWebSocketMessageToCompress()` configures a threshold for
compressing outbound web socket messages. Configure this with 0L to always compress outbound
messages and `Long.MAX_VALUE` to never compress outbound messages. The default is 1024L which
compresses messages of size 1 KiB and larger. (Inbound messages are compressed or not based on
the web socket server's configuration.)
* New: Defer constructing `Inflater` and `Deflater` instances until they are needed. This saves
memory if web socket compression is negotiated but not used.
## Version 4.5.0-RC1
_2020-03-17_
**This release candidate turns on web socket compression.**
The [spec][rfc_7692] includes a sophisticated mechanism for client and server to negotiate
compression features. We strive to offer great performance in our default configuration and so we're
making compression the default for everyone starting with this release candidate.
Please be considerate of your servers and their operators as you roll out this release. Compression
saves bandwidth but it costs CPU and memory! If you run into a problem you may need to adjust or
disable the `permessage-deflate` compression settings on your server.
Note that OkHttp won't use compression when sending messages smaller than 1 KiB.
* Fix: Don't crash when the URL hostname contains an underscore on Android.
* Fix: Change HTTP/2 to use a daemon thread for its socket reader. If you've ever seen a command
line application hang after all of the work is done, it may be due to a non-daemon thread like
this one.
* New: Include suppressed exceptions when all routes to a target service fail.
## Version 4.4.1
_2020-03-08_
* Fix: Don't reuse a connection on redirect if certs match but DNS does not. For better
locality and performance OkHttp attempts to use the same pooled connection across redirects and
follow-ups. It independently shares connections when the IP addresses and certificates match,
even if the host names do not. In 4.4.0 we introduced a regression where we shared a connection
when certificates matched but the DNS addresses did not. This would only occur when following a
redirect from one hostname to another, and where both hosts had common certificates.
* Fix: Don't fail on a redirect when a client has configured a 'trust everything' trust manager.
Typically this would cause certain redirects to fail in debug and development configurations.
## Version 4.4.0
_2020-02-17_
* New: Support `canceled()` as an event that can be observed by `EventListener`. This should be
useful for splitting out canceled calls in metrics.
* New: Publish a [bill of materials (BOM)][bom] for OkHttp. Depend on this from Gradle or Maven to
keep all of your OkHttp artifacts on the same version, even if they're declared via transitive
dependencies. You can even omit versions when declaring other OkHttp dependencies.
```kotlin
dependencies {
api(platform("com.squareup.okhttp3:okhttp-bom:4.4.0"))
api("com.squareup.okhttp3:okhttp") // No version!
api("com.squareup.okhttp3:logging-interceptor") // No version!
}
```
* New: Upgrade to Okio 2.4.3.
```kotlin
implementation("com.squareup.okio:okio:2.4.3")
```
* Fix: Limit retry attempts for HTTP/2 `REFUSED_STREAM` and `CANCEL` failures.
* Fix: Retry automatically when incorrectly sharing a connection among multiple hostnames. OkHttp
shares connections when hosts share both IP addresses and certificates, such as `squareup.com`
and `www.squareup.com`. If a server refuses such sharing it will return HTTP 421 and OkHttp will
automatically retry on an unshared connection.
* Fix: Don't crash if a TLS tunnel's response body is truncated.
* Fix: Don't track unusable routes beyond their usefulness. We had a bug where we could track
certain bad routes indefinitely; now we only track the ones that could be necessary.
* Fix: Defer proxy selection until a proxy is required. This saves calls to `ProxySelector` on
calls that use a pooled connection.
## Version 4.3.1
_2020-01-07_
* Fix: Don't crash with a `NullPointerException` when a web socket is closed before it connects.
This regression was introduced in OkHttp 4.3.0.
* Fix: Don't crash with an `IllegalArgumentException` when using custom trust managers on
Android 10. Android uses reflection to look up a magic `checkServerTrusted()` method and we
didn't have it.
* Fix: Explicitly specify the remote server name when making HTTPS connections on Android 5. In
4.3.0 we introduced a regression where server name indication (SNI) was broken on Android 5.
## Version 4.3.0
_2019-12-31_
* Fix: Degrade HTTP/2 connections after a timeout. When an HTTP/2 stream times out it may impact
the stream only or the entire connection. With this fix OkHttp will now send HTTP/2 pings after
a stream timeout to determine whether the connection should remain eligible for pooling.
* Fix: Don't call `EventListener.responseHeadersStart()` or `responseBodyStart()` until bytes have
been received. Previously these events were incorrectly sent too early, when OkHttp was ready to
read the response headers or body, which mislead tracing tools. Note that the `responseFailed()`
event always used to follow one of these events; now it may be sent without them.
* New: Upgrade to Kotlin 1.3.61.
* New: Match any number of subdomains with two asterisks in `CertificatePinner`. For example,
`**.squareup.com` matches `us-west.www.squareup.com`, `www.squareup.com` and `squareup.com`.
* New: Share threads more aggressively between OkHttp's HTTP/2 connections, connection pool,
web sockets, and cache. OkHttp has a new internal task runner abstraction for managed task
scheduling. In your debugger you will see new thread names and more use of daemon threads.
* Fix: Don't drop callbacks on unexpected exceptions. When an interceptor throws an unchecked
exception the callback is now notified that the call was canceled. The exception is still sent
to the uncaught exception handler for reporting and recovery.
* Fix: Un-deprecate `MockResponse.setHeaders()` and other setters. These were deprecated in OkHttp
4.0 but that broke method chaining for Java callers.
* Fix: Don't crash on HTTP/2 HEAD requests when the `Content-Length` header is present but is not
consistent with the length of the response body.
* Fix: Don't crash when converting a `HttpUrl` instance with an unresolvable hostname to a URI.
The new behavior strips invalid characters like `"` and `{` from the hostname before converting.
* Fix: Undo a performance regression introduced in OkHttp 4.0 caused by differences in behavior
between Kotlin's `assert()` and Java's `assert()`. (Kotlin always evaluates the argument; Java
only does when assertions are enabled.)
* Fix: Honor `RequestBody.isOneShot()` in `HttpLoggingInterceptor`.
## Version 4.2.2
_2019-10-06_
* Fix: When closing a canceled HTTP/2 stream, don't send the `END_STREAM` flag. This could cause
the server to incorrectly interpret the stream as having completed normally. This is most useful
when a request body needs to cancel its own call.
## Version 4.2.1
_2019-10-02_
* Fix: In 4.1.0 we introduced a performance regression that prevented connections from being
pooled in certain situations. We have good test coverage for connection pooling but we missed
this because it only occurs if you have proxy configured and you share a connection pool among
multiple `OkHttpClient` instances.
This particularly-subtle bug was caused by us assigning each `OkHttpClient` instance its own
`NullProxySelector` when an explicit proxy is configured. But we don't share connections when
the proxy selectors are different. Ugh!
## Version 4.2.0
_2019-09-10_
* New: API to decode a certificate and private key to create a `HeldCertificate`. This accepts a
string containing both a certificate and PKCS #8-encoded private key.
```kotlin
val heldCertificate = HeldCertificate.decode("""
|-----BEGIN CERTIFICATE-----
|MIIBYTCCAQegAwIBAgIBKjAKBggqhkjOPQQDAjApMRQwEgYDVQQLEwtlbmdpbmVl
|cmluZzERMA8GA1UEAxMIY2FzaC5hcHAwHhcNNzAwMTAxMDAwMDA1WhcNNzAwMTAx
|MDAwMDEwWjApMRQwEgYDVQQLEwtlbmdpbmVlcmluZzERMA8GA1UEAxMIY2FzaC5h
|cHAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASda8ChkQXxGELnrV/oBnIAx3dD
|ocUOJfdz4pOJTP6dVQB9U3UBiW5uSX/MoOD0LL5zG3bVyL3Y6pDwKuYvfLNhoyAw
|HjAcBgNVHREBAf8EEjAQhwQBAQEBgghjYXNoLmFwcDAKBggqhkjOPQQDAgNIADBF
|AiAyHHg1N6YDDQiY920+cnI5XSZwEGhAtb9PYWO8bLmkcQIhAI2CfEZf3V/obmdT
|yyaoEufLKVXhrTQhRfodTeigi4RX
|-----END CERTIFICATE-----
|-----BEGIN PRIVATE KEY-----
|MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCA7ODT0xhGSNn4ESj6J
|lu/GJQZoU9lDrCPeUcQ28tzOWw==
|-----END PRIVATE KEY-----
""".trimMargin())
val handshakeCertificates = HandshakeCertificates.Builder()
.heldCertificate(heldCertificate)
.build()
val server = MockWebServer()
server.useHttps(handshakeCertificates.sslSocketFactory(), false)
```
Get these strings with `HeldCertificate.certificatePem()` and `privateKeyPkcs8Pem()`.
* Fix: Handshake now returns peer certificates in canonical order: each certificate is signed by
the certificate that follows and the last certificate is signed by a trusted root.
* Fix: Don't lose HTTP/2 flow control bytes when incoming data races with a stream close. If this
happened enough then eventually the connection would stall.
* Fix: Acknowledge and apply inbound HTTP/2 settings atomically. Previously we had a race where we
could use new flow control capacity before acknowledging it, causing strict HTTP/2 servers to
fail the call.
## Version 4.1.1
_2019-09-05_
* Fix: Don't drop repeated headers when validating cached responses. In our Kotlin upgrade we
introduced a regression where we iterated the number of unique header names rather than then
number of unique headers. If you're using OkHttp's response cache this may impact you.
## Version 4.1.0
_2019-08-12_
[brotli]: https://github.com/google/brotli
* **OkHttp's new okhttp-brotli module implements Brotli compression.** Install the interceptor to
enable [Brotli compression][brotli], which compresses 5-20% smaller than gzip.
```
val client = OkHttpClient.Builder()
.addInterceptor(BrotliInterceptor)
.build()
```
This artifact has a dependency on Google's Brotli decoder (95 KiB).
* New: `EventListener.proxySelectStart()`, `proxySelectEnd()` events give visibility into the
proxy selection process.
* New: `Response.byteString()` reads the entire response into memory as a byte string.
* New: `OkHttpClient.x509TrustManager` accessor.
* New: Permit [new WebSocket response codes][iana_websocket]: 1012 (Service Restart), 1013 (Try
Again Later), and 1014 (invalid response from the upstream).
* New: Build with Kotlin 1.3.41, BouncyCastle 1.62, and Conscrypt 2.2.1.
* Fix: Recover gracefully when a coalesced connection immediately goes unhealthy.
* Fix: Defer the `SecurityException` when looking up the default proxy selector.
* Fix: Don't use brackets formatting IPv6 host names in MockWebServer.
* Fix: Don't permit cache iterators to remove entries that are being written.
## Version 4.0.1
_2019-07-10_
* Fix: Tolerate null-hostile lists in public API. Lists created with `List.of(...)` don't like it
when you call `contains(null)` on them!
* Fix: Retain binary-compatibility in `okhttp3.internal.HttpHeaders.hasBody()`. Some unscrupulous
coders call this and we don't want their users to suffer.
## Version 4.0.0
_2019-06-26_
**This release upgrades OkHttp to Kotlin.** We tried our best to make fast and safe to upgrade
from OkHttp 3.x. We wrote an [upgrade guide][upgrading_to_okhttp_4] to help with the migration and a
[blog post][okhttp4_blog_post] to explain it.
* Fix: Target Java 8 bytecode for Java and Kotlin.
## Version 4.0.0-RC3
_2019-06-24_
* Fix: Retain binary-compatibility in `okhttp3.internal.HttpMethod`. Naughty third party SDKs
import this and we want to ease upgrades for their users.
## Version 4.0.0-RC2
_2019-06-21_
* New: Require Kotlin 1.3.40.
* New: Change the Kotlin API from `File.toRequestBody()` to `File.asRequestBody()` and
`BufferedSource.toResponseBody()` to `BufferedSource.asResponseBody()`. If the returned value
is a view of what created it, we use _as_.
* Fix: Permit response codes of zero for compatibility with OkHttp 3.x.
* Fix: Change the return type of `MockWebServer.takeRequest()` to be nullable.
* Fix: Make `Call.clone()` public to Kotlin callers.
## Version 4.0.0-RC1
_2019-06-03_
* First stable preview of OkHttp 4.
## Version 3.x
[Change log](https://square.github.io/okhttp/changelog_3x/)
[bom]: https://docs.gradle.org/6.2/userguide/platforms.html#sub:bom_import
[bouncy_castle_releases]: https://www.bouncycastle.org/releasenotes.html
[CVE-2021-0341]: https://nvd.nist.gov/vuln/detail/CVE-2021-0341
[CVE-2022-24329]: https://nvd.nist.gov/vuln/detail/CVE-2022-24329
[dev_server]: https://github.com/square/okhttp/blob/482f88300f78c3419b04379fc26c3683c10d6a9d/samples/guide/src/main/java/okhttp3/recipes/kt/DevServer.kt
[fun_interface]: https://kotlinlang.org/docs/reference/fun-interfaces.html
[iana_websocket]: https://www.iana.org/assignments/websocket/websocket.txt
[jetty_8_252]: https://webtide.com/jetty-alpn-java-8u252/
[kotlin_1_3_71]: https://github.com/JetBrains/kotlin/releases/tag/v1.3.71
[kotlin_1_4_10]: https://github.com/JetBrains/kotlin/releases/tag/v1.4.10
[kotlin_1_6_20]: https://github.com/JetBrains/kotlin/releases/tag/v1.6.20
[kotlin_1_8_21]: https://github.com/JetBrains/kotlin/releases/tag/v1.8.21
[legacy_interceptor]: https://gist.github.com/swankjesse/80135f4e03629527e723ab3bcf64be0b
[okhttp4_blog_post]: https://cashapp.github.io/2019-06-26/okhttp-4-goes-kotlin
[okio.FileSystem]: https://square.github.io/okio/file_system/
[okio_2_6_0]: https://square.github.io/okio/changelog/#version-260
[okio_2_7_0]: https://square.github.io/okio/changelog/#version-270
[okio_3_0_0]: https://square.github.io/okio/changelog/#version-300
[okio_3_2_0]: https://square.github.io/okio/changelog/#version-320
[okio_3_6_0]: https://square.github.io/okio/changelog/#version-360
[public_suffix]: https://publicsuffix.org/
[rfc_2045]: https://tools.ietf.org/html/rfc2045
[rfc_7231_647]: https://tools.ietf.org/html/rfc7231#section-6.4.7
[rfc_7692]: https://tools.ietf.org/html/rfc7692
[semver]: https://semver.org/
[upgrading_to_okhttp_4]: https://square.github.io/okhttp/upgrading_to_okhttp_4/
================================================
FILE: docs/changelogs/upgrading_to_okhttp_4.md
================================================
Upgrading to OkHttp 4
=====================
OkHttp 4.x upgrades our implementation language from Java to Kotlin and keeps everything else the
same. We’ve chosen Kotlin because it gives us powerful new capabilities while integrating closely
with Java.
We spent a lot of time and energy on retaining strict compatibility with OkHttp 3.x. We’re even
keeping the package name the same: `okhttp3`!
There are three kinds of compatibility we’re tracking:
* **Binary compatibility** is the ability to compile a program against OkHttp 3.x, and then to run
it against OkHttp 4.x. We’re using the excellent [japicmp][japicmp] library via its
[Gradle plugin][japicmp_gradle] to enforce binary compatibility.
* **Java source compatibility** is the ability to upgrade Java uses of OkHttp 3.x to 4.x without
changing `.java` files.
* **Kotlin source compatibility** is the ability to upgrade Kotlin uses of OkHttp 3.x to 4.x
without changing `.kt` files.
With a few small exceptions (below), OkHttp 4.x is both binary- and Java source-compatible with
OkHttp 3.x. You can use an OkHttp 4.x .jar file with applications or libraries built for OkHttp 3.x.
OkHttp is **not** source-compatible for Kotlin callers, but upgrading should be automatic thanks to
Kotlin’s powerful deprecation features. Most developers should be able to use IntelliJ’s _Code
Cleanup_ for a safe and fast upgrade.
Backwards-Incompatible Changes
------------------------------
#### OkHttpClient final methods
`OkHttpClient` has 26 accessors like `interceptors()` and `writeTimeoutMillis()` that were non-final
in OkHttp 3.x and are final in 4.x. These were made non-final for use with mocking frameworks like
[Mockito][mockito]. We believe subtyping `OkHttpClient` is the wrong way to test with OkHttp. If
you must, mock `Call.Factory` which is the interface that `OkHttpClient` implements.
#### Internal API changes
The `okhttp3.internal` package is not a published API and we change it frequently without warning.
Depending on code in this package is bad and will cause you problems with any upgrade! But the 4.x
will be particularly painful to naughty developers that import from this package! We changed a lot
to take advantage of sweet Kotlin features.
#### Credentials.basic()
The username and password parameters to `Credentials.basic()` are now non-null strings. In OkHttp
3.x, null would yield a username or password of "null".
#### HttpUrl.queryParameterValues()
The return type of `HttpUrl.queryParameterValues()` is `List`. Lists that may contain null
are uncommon and Kotlin callers may have incorrectly assigned the result to `List`.
Code Cleanup
------------
IntelliJ and Android Studio offer a **Code Cleanup** feature that will automatically update
deprecated APIs with their replacements. Access this feature from the _Search Anywhere_ dialog
(double-press shift) or under the _Analyze_ menu.
We’ve included deprecated APIs in OkHttp 4.0 because they make migration easy. We will remove them
in a future release! If you’re skipping releases, it’ll be much easier if you upgrade to OkHttp 4.0
as an intermediate step.
#### Vars and Vals
Java doesn’t have language support for properties so developers make do with getters and setters.
Kotlin does have properties and we take advantage of them in OkHttp.
* **Address**: certificatePinner, connectionSpecs, dns, hostnameVerifier, protocols, proxy,
proxyAuthenticator, proxySelector, socketFactory, sslSocketFactory, url
* **Cache**: directory
* **CacheControl**: immutable, maxAgeSeconds, maxStaleSeconds, minFreshSeconds, mustRevalidate,
noCache, noStore, noTransform, onlyIfCached, sMaxAgeSeconds
* **Challenge**: authParams, charset, realm, scheme
* **CipherSuite**: javaName
* **ConnectionSpec**: cipherSuites, supportsTlsExtensions, tlsVersions
* **Cookie**: domain, expiresAt, hostOnly, httpOnly, name, path, persistent, value
* **Dispatcher**: executorService
* **FormBody**: size
* **Handshake**: cipherSuite, localCertificates, localPrincipal, peerCertificates, peerPrincipal,
tlsVersion
* **HandshakeCertificates**: keyManager, trustManager
* **Headers**: size
* **HeldCertificate**: certificate, keyPair
* **HttpLoggingInterceptor**: level
* **HttpUrl**: encodedFragment, encodedPassword, encodedPath, encodedPathSegments, encodedQuery,
encodedUsername, fragment, host, password, pathSegments, pathSize, port, query,
queryParameterNames, querySize, scheme, username
* **MockResponse**: headers, http2ErrorCode, socketPolicy, status, trailers
* **MockWebServer**: bodyLimit, port, protocolNegotiationEnabled, protocols, requestCount,
serverSocketFactory
* **MultipartBody.Part**: body, headers
* **MultipartBody.**: boundary, parts, size, type
* **OkHttpClient**: authenticator, cache, callTimeoutMillis, certificatePinner,
connectTimeoutMillis, connectionPool, connectionSpecs, cookieJar, dispatcher, dns,
eventListenerFactory, followRedirects, followSslRedirects, hostnameVerifier, interceptors,
networkInterceptors, pingIntervalMillis, protocols, proxy, proxyAuthenticator, proxySelector,
readTimeoutMillis, retryOnConnectionFailure, socketFactory, sslSocketFactory, writeTimeoutMillis
* **PushPromise**: headers, method, path, response
* **Request**: body, cacheControl, headers, method, url
* **Response**: body, cacheControl, cacheResponse, code, handshake, headers, message,
networkResponse, priorResponse, protocol, receivedResponseAtMillis, request, sentRequestAtMillis
* **Route**: address, proxy, socketAddress
* **TlsVersion**: javaName
#### Renamed Functions
* **Headers.of()**: for symmetry with `listOf()`, `setOf()`, etc., we’ve replaced
`Headers.of(String...)` with `headersOf(vararg String)`.
#### Extension Functions
We’ve migrated from static functions to extension functions where we think they fit.
| Java | Kotlin |
| :---------------------------------- | :------------------------------ |
| Handshake.get(SSLSession) | SSLSession.handshake() |
| Headers.of(Map) | Map.toHeaders() |
| HttpUrl.get(String) | String.toHttpUrl() |
| HttpUrl.get(URI) | URI.toHttpUrlOrNull() |
| HttpUrl.get(URL) | URL.toHttpUrlOrNull() |
| HttpUrl.parse(String) | String.toHttpUrlOrNull() |
| HttpUrl.uri() | HttpUrl.toUri() |
| HttpUrl.url() | HttpUrl.toUrl() |
| MediaType.get(String) | String.toMediaType() |
| MediaType.parse(String) | String.toMediaTypeOrNull() |
| RequestBody.create(ByteArray) | ByteArray.toRequestBody() |
| RequestBody.create(ByteString) | ByteString.toRequestBody() |
| RequestBody.create(File) | File.asRequestBody() |
| RequestBody.create(String) | String.toRequestBody() |
| ResponseBody.create(BufferedSource) | BufferedSource.asResponseBody() |
| ResponseBody.create(ByteArray) | ByteArray.toResponseBody() |
| ResponseBody.create(ByteString) | ByteString.toResponseBody() |
| ResponseBody.create(String) | String.toResponseBody() |
SAM Conversions
---------------
When you use Java APIs from Kotlin you can operate on Java interfaces as if they were Kotlin
lambdas. The [feature][java_sams] is available for interfaces that define a Single Abstract Method
(SAM).
But when you use Kotlin APIs from Kotlin there’s no automatic conversion. Code that used SAM lambdas
with OkHttp 3.x: must use `object :` with OkHttp 4.x:
Kotlin calling OkHttp 3.x:
```kotlin
val client = OkHttpClient.Builder()
.dns { hostname -> InetAddress.getAllByName(hostname).toList() }
.build()
```
Kotlin calling OkHttp 4.x:
```kotlin
val client = OkHttpClient.Builder()
.dns(object : Dns {
override fun lookup(hostname: String) =
InetAddress.getAllByName(hostname).toList()
})
.build()
```
SAM conversion impacts these APIs:
* Authenticator
* Dispatcher.setIdleCallback(Runnable)
* Dns
* EventListener.Factory
* HttpLoggingInterceptor.Logger
* LoggingEventListener.Factory
* OkHttpClient.Builder.hostnameVerifier(HostnameVerifier)
JetBrains [is working on][kotlin_sams] SAM conversions of Kotlin interfaces. Expect it in a future
release of the Kotlin language.
Companion Imports
-----------------
The equivalent of static methods in Java is companion object functions in Kotlin. The bytecode is
the same but `.kt` files now need `Companion` in the import.
This works with OkHttp 3.x:
```kotlin
import okhttp3.CipherSuite.forJavaName
```
But OkHttp 4.x needs a `Companion`:
```kotlin
import okhttp3.CipherSuite.Companion.forJavaName
```
In the unlikely event that you have a lot of these, run this:
```bash
sed -i "" \
's/^\(import okhttp3\.[^.]*\)\.\([a-z][a-zA-Z]*\)$/\1.Companion.\2/g' \
`find . -name "*.kt"`
```
Advanced Profiling
------------------
Android Studio’s Advanced Profiling feature rewrites OkHttp bytecode for instrumentation.
Unfortunately it crashes on OkHttp 4.x’s bytecode. Until [Google’s bug][advanced_profiling_bug] is
fixed you must disable advanced profiling in Android Studio.

R8 / ProGuard
-------------
R8 and ProGuard are both code optimizers for `.class` files.
R8 is the [default optimizer][r8] in Android Studio 3.4 and newer. It works well with all
releases of OkHttp.
ProGuard was the previous default. We’re [tracking problems][proguard_problems] with interactions
between ProGuard, OkHttp 4.x, and Kotlin-originated `.class` files. Make sure you’re on the latest
release if you’re using ProGuard,
Gradle
------
OkHttp 4’s minimum requirements are Java 8+ and Android 5+. These requirements were
[first introduced][require_android_5] with OkHttp 3.13.
Here’s what you need in `build.gradle` to target Java 8 byte code for Kotlin, Java, and Android
plugins respectively.
```groovy
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
compileJava {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
```
[advanced_profiling_bug]: https://issuetracker.google.com/issues/135141615
[japicmp]: https://github.com/siom79/japicmp
[japicmp_gradle]: https://github.com/melix/japicmp-gradle-plugin
[java_sams]: https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
[kotlin_sams]: https://youtrack.jetbrains.com/issue/KT-11129
[mockito]: https://site.mockito.org/
[proguard_problems]: https://github.com/square/okhttp/issues/5167
[require_android_5]: https://code.cash.app/okhttp-3-13-requires-android-5
[r8]: https://developer.android.com/studio/releases#r8-default
================================================
FILE: docs/contribute/code_of_conduct.md
================================================
Open Source Code of Conduct
===========================
At Square, we are committed to contributing to the open source community and simplifying the process
of releasing and managing open source software. We’ve seen incredible support and enthusiasm from
thousands of people who have already contributed to our projects — and we want to ensure our community
continues to be truly open for everyone.
This code of conduct outlines our expectations for participants, as well as steps to reporting
unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and
expect our code of conduct to be honored.
Square’s open source community strives to:
* **Be open**: We invite anyone to participate in any aspect of our projects. Our community is
open, and any responsibility can be carried by a contributor who demonstrates the required
capacity and competence.
* **Be considerate**: People use our work, and we depend on the work of others. Consider users and
colleagues before taking action. For example, changes to code, infrastructure, policy, and
documentation may negatively impact others.
* **Be respectful**: We expect people to work together to resolve conflict, assume good intentions,
and act with empathy. Do not turn disagreements into personal attacks.
* **Be collaborative**: Collaboration reduces redundancy and improves the quality of our work. We
strive for transparency within our open source community, and we work closely with upstream
developers and others in the free software community to coordinate our efforts.
* **Be pragmatic**: Questions are encouraged and should be asked early in the process to avoid
problems later. Be thoughtful and considerate when seeking out the appropriate forum for your
questions. Those who are asked should be responsive and helpful.
* **Step down considerately**: Members of every project come and go. When somebody leaves or
disengages from the project, they should make it known and take the proper steps to ensure that
others can pick up where they left off.
This code is not exhaustive or complete. It serves to distill our common understanding of a
collaborative, shared environment, and goals. We expect it to be followed in spirit as much as in
the letter.
Diversity Statement
-------------------
We encourage everyone to participate and are committed to building a community for all. Although we
may not be able to satisfy everyone, we all agree that everyone is equal.
Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone
has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do
our best to right the wrong.
Although this list cannot be exhaustive, we explicitly honor diversity in age, culture, ethnicity,
gender identity or expression, language, national origin, political beliefs, profession, race,
religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate
discrimination based on any of the protected characteristics above, including participants with
disabilities.
Reporting Issues
----------------
If you experience or witness unacceptable behavior — or have any other concerns — please report it by
emailing [codeofconduct@squareup.com][codeofconduct_at]. For more details, please see our Reporting
Guidelines below.
Thanks
------
Some of the ideas and wording for the statements and guidelines above were based on work by the
[Twitter][twitter_coc], [Ubuntu][ubuntu_coc], [GDC][gdc_coc], and [Django][django_coc] communities.
We are thankful for their work.
Reporting Guide
---------------
If you experience or witness unacceptable behavior — or have any other concerns — please report it by
emailing [codeofconduct@squareup.com][codeofconduct_at]. All reports will be handled with
discretion.
In your report please include:
* Your contact information.
* Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional
witnesses, please include them as well.
* Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly
available record (e.g. a mailing list archive or a public IRC logger), please include a link.
* Any additional information that may be helpful.
After filing a report, a representative from the Square Code of Conduct committee will contact you
personally. The committee will then review the incident, follow up with any additional questions,
and make a decision as to how to respond.
Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual
engages in unacceptable behavior, the Square Code of Conduct committee may take any action they deem
appropriate, up to and including a permanent ban from all of Square spaces without warning.
[codeofconduct_at]: mailto:codeofconduct@squareup.com
[twitter_coc]: https://github.com/twitter/code-of-conduct/blob/master/code-of-conduct.md
[ubuntu_coc]: https://ubuntu.com/community/code-of-conduct
[gdc_coc]: https://www.gdconf.com/code-of-conduct
[django_coc]: https://www.djangoproject.com/conduct/reporting/
================================================
FILE: docs/contribute/concurrency.md
================================================
Concurrency
===========
This document describes the concurrency considerations for http/2 connections and the connection pool within OkHttp.
## HTTP/2 Connections
The HttpURLConnection API is a blocking API. You make a blocking write to send a request, and a blocking read to receive the response.
#### Blocking APIs
Blocking APIs are convenient because you get top-to-bottom procedural code without indirection. Network calls work like regular method calls: ask for data and it is returned. If the request fails, you get a stacktrace right where the call was made.
Blocking APIs may be inefficient because you hold a thread idle while waiting on the network. Threads are expensive because they have both a memory overhead and a context-switching overhead.
#### Framed protocols
Framed protocols like http/2 don't lend themselves to blocking APIs. Each application-layer thread wants to do blocking I/O for a specific stream, but the streams are multiplexed on the socket. You can't just talk to the socket, you need to cooperate with the other application-layer threads that you're sharing it with.
Framing rules make it impractical to implement http/2 correctly on a single blocking thread. The flow-control features introduce feedback between reads and writes, requiring writes to acknowledge reads and reads to throttle writes.
In OkHttp we expose a blocking API over a framed protocol. This document explains the code and policy that makes that work.
### Threads
#### Application's calling thread
The application-layer must block on writing I/O. We can't return from a write until we've pushed its bytes onto the socket. Otherwise, if the write fails we are unable to deliver its IOException to the application. We would have told the application layer that the write succeeded, but it didn't!
The application-layer can also do blocking reads. If the application asks to read and there's nothing available, we need to hold that thread until either the bytes arrive, the stream is closed, or a timeout elapses. If we get bytes but there's nobody asking for them, we buffer them. We don't consider bytes as delivered for flow control until they're consumed by the application.
Consider an application streaming a video over http/2. Perhaps the user pauses the video and the application stops reading bytes from this stream. The buffer will fill up, and flow control prevents the server from sending more data on this stream. When the user unpauses her video the buffer drains, the read is acknowledged, and the server proceeds to stream data.
#### Shared reader thread
We can't rely on application threads to read data from the socket. Application threads are transient: sometimes they're reading and writing and sometimes they're off doing application-layer things. But the socket is permanent, and it needs constant attention: we dispatch all incoming frames so the connection is good-to-go when the application layer needs it.
So we have a dedicated thread for every socket that just reads frames and dispatches them.
The reader thread must never run application-layer code. Otherwise one slow stream can hold up the entire connection.
Similarly, the reader thread must never block on writing because this can deadlock the connection. Consider a client and server that both violate this rule. If you get unlucky, they could fill up their TCP buffers (so that writes block) and then use their reader threads to write a frame. Nobody is reading on either end, and the buffers are never drained.
#### Do-stuff-later pool
Sometimes there's an action required like calling the application layer or responding to a ping, and the thread discovering the action is not the thread that should do the work. We enqueue a runnable on this executor and it gets handled by one of the executor's threads.
### Locks
We have 3 different things that we synchronize on.
#### Http2Connection
This lock guards internal state of each connection. This lock is never held for blocking operations. That means that we acquire the lock, read or write a few fields and release the lock. No I/O and no application-layer callbacks.
#### Http2Stream
This lock guards the internal state of each stream. As above, it is never held for blocking operations. When we need to hold an application thread to block a read, we use wait/notify on this lock. This works because the lock is released while `wait()` is waiting.
#### Http2Writer
Socket writes are guarded by the Http2Writer. Only one stream can write at a time so that messages are not interleaved. Writes are either made by application-layer threads or the do-stuff-later pool.
### Holding multiple locks
You're allowed to take the Http2Connection lock while holding the Http2Writer lock. But not vice-versa. Because taking the Http2Writer lock can block.
This is necessary for bookkeeping when creating new streams. Correct framing requires that stream IDs are sequential on the socket, so we need to bundle assigning the ID with sending the `SYN_STREAM` frame.
## Connection Pool
A primary responsibility for any HTTP client is to efficiently manage network connections. Creating and establishing new connections require a fair amount of overhead and added latency. OkHttp will make every effort to reuse existing connections to avoid this overhead and added latency.
Every OkHttpClient uses a connection pool. Its job is to maintain a reference to all open connections. When an HTTP request is started, OkHttp will attempt to reuse an existing connection from the pool. If there are no existing connections, a new one is created and put into the connection pool. For HTTP/2, the connection can be reused immediately. For HTTP/1, the request must be completed before it can be reused.
Since HTTP requests frequently happen in parallel, connection pooling must be thread-safe.
These are the primary classes involved with establishing, sharing, and terminating connections:
* **RealConnectionPool** manages reuse of HTTP and HTTP/2 connections for reduced latency. Every OkHttpClient has one, and its lifetime spans the lifetime of the OkHttpClient.
* **RealConnection** is the socket and streams of an HTTP/1 or HTTP/2 connection. These are created on demand to fulfill HTTP requests. They may be reused for many HTTP request/response exchanges. Their lifetime is typically shorter than a connection pool.
* **Exchange** carries a single HTTP request/response pair.
* **ExchangeFinder** chooses which connection carries each exchange. Where possible it will use the same connection for all exchanges in a single call. It prefers reusing pooled connections over establishing new connections.
#### Per-Connection Locks
Each connection has its own lock. The connections in the pool are all in a `ConcurrentLinkedQueue`. Due to data races, iterators of this queue may return removed connections. Callers must check the connection's `noNewExchanges` property before using connections from the pool.
The connection lock is never held while doing I/O (even closing a socket) to prevent contention.
A lock-per-connection is used to maximize concurrency.
================================================
FILE: docs/contribute/contributing.md
================================================
Contributing
============
Keeping the project small and stable limits our ability to accept new contributors. We are not
seeking new committers at this time, but some small contributions are welcome.
If you've found a security problem, please follow our [bug bounty][security] program.
If you've found a bug, please contribute a failing test case so we can study and fix it.
If you have a new feature idea, please build it in an external library. There are
[many libraries][works_with_okhttp] that sit on top or hook in via existing APIs. If you build
something that integrates with OkHttp, tell us so that we can link it!
Before code can be accepted all contributors must complete our
[Individual Contributor License Agreement (CLA)][cla].
Code Contributions
------------------
Get working code on a personal branch with tests passing before you submit a PR:
```
./gradlew clean check
```
Please make every effort to follow existing conventions and style in order to keep the code as
readable as possible.
Contribute code changes through GitHub by forking the repository and sending a pull request. We
squash all pull requests on merge.
Gradle Setup
------------
```
$ cat local.properties
sdk.dir=PATH_TO_ANDROID_HOME/sdk
org.gradle.caching=true
```
Running Android Tests
---------------------
$ ANDROID_SDK_ROOT=PATH_TO_ANDROID_HOME/sdk ./gradlew :android-test:connectedCheck -PandroidBuild=true
Committer's Guides
------------------
* [Concurrency][concurrency]
* [Debug Logging][debug_logging]
* [Releasing][releasing]
[cla]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
[concurrency]: https://square.github.io/okhttp/concurrency/
[debug_logging]: https://square.github.io/okhttp/debug_logging/
[releasing]: https://square.github.io/okhttp/releasing/
[security]: https://square.github.io/okhttp/security/
[works_with_okhttp]: https://square.github.io/okhttp/works_with_okhttp/
[okhttp_build]: https://github.com/square/okhttp/blob/master/okhttp/build.gradle
================================================
FILE: docs/contribute/debug_logging.md
================================================
Debug Logging
=============
OkHttp has internal APIs to enable debug logging. It uses the `java.util.logging` API which can be
tricky to configure. As a shortcut, you can paste [OkHttpDebugLogging.kt]. Then enable debug logging
for whichever features you need:
```
OkHttpDebugLogging.enableHttp2()
OkHttpDebugLogging.enableTaskRunner()
```
### Activating on Android
```
$ adb shell setprop log.tag.okhttp.Http2 DEBUG
$ adb shell setprop log.tag.okhttp.TaskRunner DEBUG
$ adb logcat '*:E' 'okhttp.Http2:D' 'okhttp.TaskRunner:D'
```
### HTTP/2 Frame Logging
This logs inbound (`<<`) and outbound (`>>`) frames for HTTP/2 connections.
```
[2020-01-01 00:00:00] >> CONNECTION 505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
[2020-01-01 00:00:00] >> 0x00000000 6 SETTINGS
[2020-01-01 00:00:00] >> 0x00000000 4 WINDOW_UPDATE
[2020-01-01 00:00:00] >> 0x00000003 47 HEADERS END_STREAM|END_HEADERS
[2020-01-01 00:00:00] << 0x00000000 6 SETTINGS
[2020-01-01 00:00:00] << 0x00000000 0 SETTINGS ACK
[2020-01-01 00:00:00] << 0x00000000 4 WINDOW_UPDATE
[2020-01-01 00:00:00] >> 0x00000000 0 SETTINGS ACK
[2020-01-01 00:00:00] << 0x00000003 322 HEADERS END_HEADERS
[2020-01-01 00:00:00] << 0x00000003 288 DATA
[2020-01-01 00:00:00] << 0x00000003 0 DATA END_STREAM
[2020-01-01 00:00:00] << 0x00000000 8 GOAWAY
[2020-01-01 00:00:05] << 0x00000000 8 GOAWAY
```
### Task Runner Logging
This logs task enqueues, starts, and finishes.
```
[2020-01-01 00:00:00] Q10000 scheduled after 0 µs: OkHttp ConnectionPool
[2020-01-01 00:00:00] Q10000 starting : OkHttp ConnectionPool
[2020-01-01 00:00:00] Q10000 run again after 300 s : OkHttp ConnectionPool
[2020-01-01 00:00:00] Q10000 finished run in 1 ms: OkHttp ConnectionPool
[2020-01-01 00:00:00] Q10001 scheduled after 0 µs: OkHttp squareup.com applyAndAckSettings
[2020-01-01 00:00:00] Q10001 starting : OkHttp squareup.com applyAndAckSettings
[2020-01-01 00:00:00] Q10003 scheduled after 0 µs: OkHttp squareup.com onSettings
[2020-01-01 00:00:00] Q10003 starting : OkHttp squareup.com onSettings
[2020-01-01 00:00:00] Q10001 finished run in 3 ms: OkHttp squareup.com applyAndAckSettings
[2020-01-01 00:00:00] Q10003 finished run in 528 µs: OkHttp squareup.com onSettings
[2020-01-01 00:00:00] Q10000 scheduled after 0 µs: OkHttp ConnectionPool
[2020-01-01 00:00:00] Q10000 starting : OkHttp ConnectionPool
[2020-01-01 00:00:00] Q10000 run again after 300 s : OkHttp ConnectionPool
[2020-01-01 00:00:00] Q10000 finished run in 739 µs: OkHttp ConnectionPool
```
[OkHttpDebugLogging.kt]: https://github.com/square/okhttp/blob/master/okhttp-testing-support/src/main/kotlin/okhttp3/OkHttpDebugLogging.kt
================================================
FILE: docs/features/caching.md
================================================
Caching
=======
OkHttp implements an optional, off by default, Cache. OkHttp aims for RFC correct and
pragmatic caching behaviour, following common real-world browser like Firefox/Chrome and
server behaviour when ambiguous.
# Basic Usage
```kotlin
private val client: OkHttpClient = OkHttpClient.Builder()
.cache(Cache(
directory = File(application.cacheDir, "http_cache"),
// $0.05 worth of phone storage in 2020
maxSize = 50L * 1024L * 1024L // 50 MiB
))
.build()
```
## EventListener events
Cache Events are exposed via the EventListener API. Typical scenarios are below.
### Cache Hit
In the ideal scenario the cache can fulfill the request without any conditional call to the network.
This will skip the normal events such as DNS, connecting to the network, and downloading the response body.
As recommended by the HTTP RFC the max age of a document is defaulted to 10% of the
document's age at the time it was served based on "Last-Modified". Default expiration dates aren't used for URIs
containing a query.
- CallStart
- **CacheHit**
- CallEnd
### Cache Miss
Under a cache miss the normal request events are seen but an additional event shows the presence of the cache.
Cache Miss will be typical if the item has not been read from the network, is uncacheable, or is past it's
lifetime based on Response cache headers.
- CallStart
- **CacheMiss**
- ProxySelectStart
- ... Standard Events ...
- CallEnd
### Conditional Cache Hit
When cache flags require checking the cache results are still valid an early cacheConditionalHit event is
received followed by a cache hit or miss. Critically in the cache hit scenario the server won’t send the response body.
The response will have non-null `cacheResponse` and `networkResponse`. The cacheResponse will be used as the top level
response only if the response code is HTTP/1.1 304 Not Modified.
- CallStart
- **CacheConditionalHit**
- ConnectionAcquired
- ... Standard Events...
- ResponseBodyEnd _(0 bytes)_
- **CacheHit**
- ConnectionReleased
- CallEnd
## Cache directory
The cache directory must be exclusively owned by a single instance.
Deleting the cache when it is no longer needed can be done. However this may delete the purpose of the cache
which is designed to persist between app restarts.
```kotlin
cache.delete()
```
## Pruning the Cache
Pruning the entire Cache to clear space temporarily can be done using evictAll.
```kotlin
cache.evictAll()
```
Removing individual items can be done using the urls iterator.
This would be typical after a user initiates a force refresh by a pull to refresh type action.
```java
val urlIterator = cache.urls()
while (urlIterator.hasNext()) {
if (urlIterator.next().startsWith("https://www.google.com/")) {
urlIterator.remove()
}
}
```
### Troubleshooting
1. Valid cacheable responses are not being cached
Make sure you are reading responses fully as unless they are read fully, cancelled or stalled Responses will not be cached.
### Overriding normal cache behaviour
See [`Cache`](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-cache/) documentation.
================================================
FILE: docs/features/calls.md
================================================
# Calls
The HTTP client’s job is to accept your request and produce its response. This is simple in theory but it gets tricky in practice.
## [Requests](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-request/)
Each HTTP request contains a URL, a method (like `GET` or `POST`), and a list of headers. Requests may also contain a body: a data stream of a specific content type.
## [Responses](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-response/)
The response answers the request with a code (like 200 for success or 404 for not found), headers, and its own optional body.
## Rewriting Requests
When you provide OkHttp with an HTTP request, you’re describing the request at a high-level: _“fetch me this URL with these headers.”_ For correctness and efficiency, OkHttp rewrites your request before transmitting it.
OkHttp may add headers that are absent from the original request, including `Content-Length`, `Transfer-Encoding`, `User-Agent`, `Host`, `Connection`, and `Content-Type`. It will add an `Accept-Encoding` header for transparent response compression unless the header is already present. If you’ve got cookies, OkHttp will add a `Cookie` header with them.
Some requests will have a cached response. When this cached response isn’t fresh, OkHttp can do a _conditional GET_ to download an updated response if it’s newer than what’s cached. This requires headers like `If-Modified-Since` and `If-None-Match` to be added.
## Rewriting Responses
If transparent compression was used, OkHttp will drop the corresponding response headers `Content-Encoding` and `Content-Length` because they don’t apply to the decompressed response body.
If a conditional GET was successful, responses from the network and cache are merged as directed by the spec.
## Follow-up Requests
When your requested URL has moved, the webserver will return a response code like `302` to indicate the document’s new URL. OkHttp will follow the redirect to retrieve a final response.
If the response issues an authorization challenge, OkHttp will ask the [`Authenticator`](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-authenticator/) (if one is configured) to satisfy the challenge. If the authenticator supplies a credential, the request is retried with that credential included.
## Retrying Requests
Sometimes connections fail: either a pooled connection was stale and disconnected, or the webserver itself couldn’t be reached. OkHttp will retry the request with a different route if one is available.
## [Calls](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-call/)
With rewrites, redirects, follow-ups and retries, your simple request may yield many requests and responses. OkHttp uses `Call` to model the task of satisfying your request through however many intermediate requests and responses are necessary. Typically this isn’t many! But it’s comforting to know that your code will continue to work if your URLs are redirected or if you failover to an alternate IP address.
Calls are executed in one of two ways:
* **Synchronous:** your thread blocks until the response is readable.
* **Asynchronous:** you enqueue the request on any thread, and get [called back](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-callback/) on another thread when the response is readable.
Calls can be canceled from any thread. This will fail the call if it hasn’t yet completed! Code that is writing the request body or reading the response body will suffer an `IOException` when its call is canceled.
## Dispatch
For synchronous calls, you bring your own thread and are responsible for managing how many simultaneous requests you make. Too many simultaneous connections wastes resources; too few harms latency.
For asynchronous calls, [`Dispatcher`](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-dispatcher/) implements policy for maximum simultaneous requests. You can set maximums per-webserver (default is 5), and overall (default is 64).
================================================
FILE: docs/features/connections.md
================================================
Connections
===========
Although you provide only the URL, OkHttp plans its connection to your webserver using three types: URL, Address, and Route.
### [URLs](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-http-url/)
URLs (like `https://github.com/square/okhttp`) are fundamental to HTTP and the Internet. In addition to being a universal, decentralized naming scheme for everything on the web, they also specify how to access web resources.
URLs are abstract:
* They specify that the call may be plaintext (`http`) or encrypted (`https`), but not which cryptographic algorithms should be used. Nor do they specify how to verify the peer's certificates (the [HostnameVerifier](https://developer.android.com/reference/javax/net/ssl/HostnameVerifier.html)) or which certificates can be trusted (the [SSLSocketFactory](https://developer.android.com/reference/org/apache/http/conn/ssl/SSLSocketFactory.html)).
* They don't specify whether a specific proxy server should be used or how to authenticate with that proxy server.
They're also concrete: each URL identifies a specific path (like `/square/okhttp`) and query (like `?q=sharks&lang=en`). Each webserver hosts many URLs.
### [Addresses](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-address/)
Addresses specify a webserver (like `github.com`) and all of the **static** configuration necessary to connect to that server: the port number, HTTPS settings, and preferred network protocols (like HTTP/2).
URLs that share the same address may also share the same underlying TCP socket connection. Sharing a connection has substantial performance benefits: lower latency, higher throughput (due to [TCP slow start](https://www.igvita.com/2011/10/20/faster-web-vs-tcp-slow-start/)) and conserved battery. OkHttp uses a [ConnectionPool](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-connection-pool/) that automatically reuses HTTP/1.x connections and multiplexes HTTP/2 connections.
In OkHttp some fields of the address come from the URL (scheme, hostname, port) and the rest come from the [OkHttpClient](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-ok-http-client/).
### [Routes](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-route/)
Routes supply the **dynamic** information necessary to actually connect to a webserver. This is the specific IP address to attempt (as discovered by a DNS query), the exact proxy server to use (if a [ProxySelector](https://developer.android.com/reference/java/net/ProxySelector.html) is in use), and which version of TLS to negotiate (for HTTPS connections).
There may be many routes for a single address. For example, a webserver that is hosted in multiple datacenters may yield multiple IP addresses in its DNS response.
In limited situations OkHttp will retry a route if connecting fails:
* When making an HTTPS connection through an HTTP proxy, the proxy may issue an authentication challenge. OkHttp will call the proxy [authenticator](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-authenticator/) and try again.
* When making TLS connections with multiple [connection specs](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-connection-spec/), these are attempted in sequence until the TLS handshake succeeds.
### [Connections](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-connection/)
When you request a URL with OkHttp, here's what it does:
1. It uses the URL and configured OkHttpClient to create an **address**. This address specifies how we'll connect to the webserver.
2. It attempts to retrieve a connection with that address from the **connection pool**.
3. If it doesn't find a connection in the pool, it selects a **route** to attempt. This usually means making a DNS request to get the server's IP addresses. It then selects a TLS version and proxy server if necessary.
4. If it's a new route, it connects by building either a direct socket connection, a TLS tunnel (for HTTPS over an HTTP proxy), or a direct TLS connection. It does TLS handshakes as necessary. This step may be retried for tunnel challenges and TLS handshake failures.
5. It sends the HTTP request and reads the response.
If there's a problem with the connection, OkHttp will select another route and try again. This allows OkHttp to recover when a subset of a server's addresses are unreachable. It's also useful when a pooled connection is stale or if the attempted TLS version is unsupported.
Once the response has been received, the connection will be returned to the pool so it can be reused for a future request. Connections are evicted from the pool after a period of inactivity.
### [Fast Fallback](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-ok-http-client/-builder/fast-fallback)
Since version 5.0, `OkHttpClient` supports fast fallback, which is our implementation of Happy Eyeballs [RFC 6555](https://datatracker.ietf.org/doc/html/rfc6555).
With fast fallback, OkHttp attempts to connect to multiple web servers concurrently. It keeps whichever route connects first and cancels all of the others. Its rules are:
* Prefer to alternate IP addresses from different address families, (IPv6 / IPv4), starting with IPv6.
* Don't start a new attempt until 250 ms after the most recent attempt was started.
* Keep whichever TCP connection succeeds first and cancel all the others.
* Race TCP only. Only attempt a TLS handshake on the winning TCP connection.
If the winner of the TCP handshake race fails to succeed in a TLS handshake, the process is restarted with the remaining routes.
================================================
FILE: docs/features/events.md
================================================
Events
======
Events allow you to capture metrics on your application’s HTTP calls. Use events to monitor:
* The size and frequency of the HTTP calls your application makes. If you’re making too many calls, or your calls are too large, you should know about it!
* The performance of these calls on the underlying network. If the network’s performance isn’t sufficient, you need to either improve the network or use less of it.
### EventListener
Subclass [EventListener](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-event-listener/) and override methods for the events you are interested in. In a successful HTTP call with no redirects or retries the sequence of events is described by this flow.

Here’s a [sample event listener](https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/PrintEventsNonConcurrent.java) that prints each event with a timestamp.
```java
class PrintingEventListener extends EventListener {
private long callStartNanos;
private void printEvent(String name) {
long nowNanos = System.nanoTime();
if (name.equals("callStart")) {
callStartNanos = nowNanos;
}
long elapsedNanos = nowNanos - callStartNanos;
System.out.printf("%.3f %s%n", elapsedNanos / 1000000000d, name);
}
@Override public void callStart(Call call) {
printEvent("callStart");
}
@Override public void callEnd(Call call) {
printEvent("callEnd");
}
@Override public void dnsStart(Call call, String domainName) {
printEvent("dnsStart");
}
@Override public void dnsEnd(Call call, String domainName, List inetAddressList) {
printEvent("dnsEnd");
}
...
}
```
We make a couple calls:
```java
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
System.out.println("REQUEST 1 (new connection)");
try (Response response = client.newCall(request).execute()) {
// Consume and discard the response body.
response.body().source().readByteString();
}
System.out.println("REQUEST 2 (pooled connection)");
try (Response response = client.newCall(request).execute()) {
// Consume and discard the response body.
response.body().source().readByteString();
}
```
And the listener prints the corresponding events:
```
REQUEST 1 (new connection)
0.000 callStart
0.010 dnsStart
0.017 dnsEnd
0.025 connectStart
0.117 secureConnectStart
0.586 secureConnectEnd
0.586 connectEnd
0.587 connectionAcquired
0.588 requestHeadersStart
0.590 requestHeadersEnd
0.591 responseHeadersStart
0.675 responseHeadersEnd
0.676 responseBodyStart
0.679 responseBodyEnd
0.679 connectionReleased
0.680 callEnd
REQUEST 2 (pooled connection)
0.000 callStart
0.001 connectionAcquired
0.001 requestHeadersStart
0.001 requestHeadersEnd
0.002 responseHeadersStart
0.082 responseHeadersEnd
0.082 responseBodyStart
0.082 responseBodyEnd
0.083 connectionReleased
0.083 callEnd
```
Notice how no connect events are fired for the second call. It reused the connection from the first request for dramatically better performance.
### EventListener.Factory
In the preceding example we used a field, `callStartNanos`, to track the elapsed time of each event. This is handy, but it won’t work if multiple calls are executing concurrently. To accommodate this, use a `Factory` to create a new `EventListener` instance for each `Call`. This allows each listener to keep call-specific state.
This [sample factory](https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/PrintEvents.java) creates a unique ID for each call and uses that ID to differentiate calls in log messages.
```java
class PrintingEventListener extends EventListener {
public static final Factory FACTORY = new Factory() {
final AtomicLong nextCallId = new AtomicLong(1L);
@Override public EventListener create(Call call) {
long callId = nextCallId.getAndIncrement();
System.out.printf("%04d %s%n", callId, call.request().url());
return new PrintingEventListener(callId, System.nanoTime());
}
};
final long callId;
final long callStartNanos;
public PrintingEventListener(long callId, long callStartNanos) {
this.callId = callId;
this.callStartNanos = callStartNanos;
}
private void printEvent(String name) {
long elapsedNanos = System.nanoTime() - callStartNanos;
System.out.printf("%04d %.3f %s%n", callId, elapsedNanos / 1000000000d, name);
}
@Override public void callStart(Call call) {
printEvent("callStart");
}
@Override public void callEnd(Call call) {
printEvent("callEnd");
}
...
}
```
We can use this listener to race a pair of concurrent HTTP requests:
```java
Request washingtonPostRequest = new Request.Builder()
.url("https://www.washingtonpost.com/")
.build();
client.newCall(washingtonPostRequest).enqueue(new Callback() {
...
});
Request newYorkTimesRequest = new Request.Builder()
.url("https://www.nytimes.com/")
.build();
client.newCall(newYorkTimesRequest).enqueue(new Callback() {
...
});
```
Running this race over home WiFi shows the Times (`0002`) completes just slightly sooner than the Post (`0001`):
```
0001 https://www.washingtonpost.com/
0001 0.000 callStart
0002 https://www.nytimes.com/
0002 0.000 callStart
0002 0.010 dnsStart
0001 0.013 dnsStart
0001 0.022 dnsEnd
0002 0.019 dnsEnd
0001 0.028 connectStart
0002 0.025 connectStart
0002 0.072 secureConnectStart
0001 0.075 secureConnectStart
0001 0.386 secureConnectEnd
0002 0.390 secureConnectEnd
0002 0.400 connectEnd
0001 0.403 connectEnd
0002 0.401 connectionAcquired
0001 0.404 connectionAcquired
0001 0.406 requestHeadersStart
0002 0.403 requestHeadersStart
0001 0.414 requestHeadersEnd
0002 0.411 requestHeadersEnd
0002 0.412 responseHeadersStart
0001 0.415 responseHeadersStart
0002 0.474 responseHeadersEnd
0002 0.475 responseBodyStart
0001 0.554 responseHeadersEnd
0001 0.555 responseBodyStart
0002 0.554 responseBodyEnd
0002 0.554 connectionReleased
0002 0.554 callEnd
0001 0.624 responseBodyEnd
0001 0.624 connectionReleased
0001 0.624 callEnd
```
The `EventListener.Factory` also makes it possible to limit metrics to a subset of calls. This one captures metrics on a random 10%:
```java
class MetricsEventListener extends EventListener {
private static final Factory FACTORY = new Factory() {
@Override public EventListener create(Call call) {
if (Math.random() < 0.10) {
return new MetricsEventListener(call);
} else {
return EventListener.NONE;
}
}
};
...
}
```
### Events with Failures
When an operation fails, a failure method is called. This is `connectFailed()` for failures while building a connection to the server, and `callFailed()` when the HTTP call fails permanently. When a failure happens it is possible that a `start` event won’t have a corresponding `end` event.

### Events with Retries and Follow-Ups
OkHttp is resilient and can automatically recover from some connectivity failures. In this case, the `connectFailed()` event is not terminal and not followed by `callFailed()`. Event listeners will receive multiple events of the same type when retries are attempted.
A single HTTP call may require follow-up requests to be made to handle authentication challenges, redirects, and HTTP-layer timeouts. In such cases multiple connections, requests, and responses may be attempted. Follow-ups are another reason a single call may trigger multiple events of the same type.

### Availability
Events is available as a public API in OkHttp 3.11. Future releases may introduce new event types; you will need to override the corresponding methods to handle them.
================================================
FILE: docs/features/https.md
================================================
HTTPS
=====
OkHttp attempts to balance two competing concerns:
* **Connectivity** to as many hosts as possible. That includes advanced hosts that run the latest versions of [boringssl](https://boringssl.googlesource.com/boringssl/) and less out of date hosts running older versions of [OpenSSL](https://www.openssl.org/).
* **Security** of the connection. This includes verification of the remote webserver with certificates and the privacy of data exchanged with strong ciphers.
When negotiating a connection to an HTTPS server, OkHttp needs to know which [TLS versions](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-tls-version/) and [cipher suites](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-cipher-suite/) to offer. A client that wants to maximize connectivity would include obsolete TLS versions and weak-by-design cipher suites. A strict client that wants to maximize security would be limited to only the latest TLS version and strongest cipher suites.
Specific security vs. connectivity decisions are implemented by [ConnectionSpec](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-connection-spec/). OkHttp includes four built-in connection specs:
* `RESTRICTED_TLS` is a secure configuration, intended to meet stricter compliance requirements.
* `MODERN_TLS` is a secure configuration that connects to modern HTTPS servers.
* `COMPATIBLE_TLS` is a secure configuration that connects to secure–but not current–HTTPS servers.
* `CLEARTEXT` is an insecure configuration that is used for `http://` URLs.
These loosely follow the model set in [Google Cloud Policies](https://cloud.google.com/load-balancing/docs/ssl-policies-concepts). We [track changes](../security/tls_configuration_history.md) to this policy.
By default, OkHttp will attempt a `MODERN_TLS` connection. However by configuring the client connectionSpecs you can allow a fall back to `COMPATIBLE_TLS` connection if the modern configuration fails.
```java
OkHttpClient client = new OkHttpClient.Builder()
.connectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS))
.build();
```
The TLS versions and cipher suites in each spec can change with each release. For example, in OkHttp 2.2 we dropped support for SSL 3.0 in response to the [POODLE](https://googleonlinesecurity.blogspot.ca/2014/10/this-poodle-bites-exploiting-ssl-30.html) attack. And in OkHttp 2.3 we dropped support for [RC4](https://en.wikipedia.org/wiki/RC4#Security). As with your desktop web browser, staying up-to-date with OkHttp is the best way to stay secure.
You can build your own connection spec with a custom set of TLS versions and cipher suites. For example, this configuration is limited to three highly-regarded cipher suites. Its drawback is that it requires Android 5.0+ and a similarly current webserver.
```java
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.cipherSuites(
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)
.build();
OkHttpClient client = new OkHttpClient.Builder()
.connectionSpecs(Collections.singletonList(spec))
.build();
```
### Debugging TLS Handshake Failures
The TLS handshake requires clients and servers to share a common TLS version and cipher suite. This
depends on the JVM or Android version, OkHttp version, and web server configuration. If there is no
common cipher suite and TLS version, your call will fail like this:
```
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x7f2719a89e80:
Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake
failure (external/openssl/ssl/s23_clnt.c:770 0x7f2728a53ea0:0x00000000)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
```
You can check a web server's configuration using [Qualys SSL Labs][qualys]. OkHttp's TLS
configuration history is [tracked here](../security/tls_configuration_history.md).
Applications expected to be installed on older Android devices should consider adopting the
[Google Play Services’ ProviderInstaller][provider_installer]. This will increase security for users
and increase connectivity with web servers.
### Certificate Pinning ([.kt][CertificatePinningKotlin], [.java][CertificatePinningJava])
By default, OkHttp trusts the certificate authorities of the host platform. This strategy maximizes connectivity, but it is subject to certificate authority attacks such as the [2011 DigiNotar attack](https://www.computerworld.com/article/2510951/cybercrime-hacking/hackers-spied-on-300-000-iranians-using-fake-google-certificate.html). It also assumes your HTTPS servers’ certificates are signed by a certificate authority.
Use [CertificatePinner](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-certificate-pinner/) to restrict which certificates and certificate authorities are trusted. Certificate pinning increases security, but limits your server team’s abilities to update their TLS certificates. **Do not use certificate pinning without the blessing of your server’s TLS administrator!**
=== ":material-language-kotlin: Kotlin"
```kotlin
private val client = OkHttpClient.Builder()
.certificatePinner(
CertificatePinner.Builder()
.add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
.build())
.build()
fun run() {
val request = Request.Builder()
.url("https://publicobject.com/robots.txt")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for (certificate in response.handshake!!.peerCertificates) {
println(CertificatePinner.pin(certificate))
}
}
}
```
=== ":material-language-java: Java"
```java
private final OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(
new CertificatePinner.Builder()
.add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
.build())
.build();
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://publicobject.com/robots.txt")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
for (Certificate certificate : response.handshake().peerCertificates()) {
System.out.println(CertificatePinner.pin(certificate));
}
}
}
```
### Customizing Trusted Certificates ([.kt][CustomTrustKotlin], [.java][CustomTrustJava])
The full code sample shows how to replace the host platform’s certificate authorities with your own set. As above, **do not use custom certificates without the blessing of your server’s TLS administrator!**
=== ":material-language-kotlin: Kotlin"
```kotlin
private val client: OkHttpClient
init {
val trustManager = trustManagerForCertificates(trustedCertificatesInputStream())
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf(trustManager), null)
val sslSocketFactory = sslContext.socketFactory
client = OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustManager)
.build()
}
fun run() {
val request = Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for ((name, value) in response.headers) {
println("$name: $value")
}
println(response.body!!.string())
}
}
/**
* Returns an input stream containing one or more certificate PEM files. This implementation just
* embeds the PEM files in Java strings; most applications will instead read this from a resource
* file that gets bundled with the application.
*/
private fun trustedCertificatesInputStream(): InputStream {
... // Full source omitted. See sample.
}
private fun trustManagerForCertificates(inputStream: InputStream): X509TrustManager {
... // Full source omitted. See sample.
}
```
=== ":material-language-java: Java"
```java
private final OkHttpClient client;
public CustomTrust() {
X509TrustManager trustManager;
SSLSocketFactory sslSocketFactory;
try {
trustManager = trustManagerForCertificates(trustedCertificatesInputStream());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustManager }, null);
sslSocketFactory = sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustManager)
.build();
}
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
}
private InputStream trustedCertificatesInputStream() {
... // Full source omitted. See sample.
}
public SSLContext sslContextForTrustedCertificates(InputStream in) {
... // Full source omitted. See sample.
}
```
[CustomTrustJava]: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java
[CustomTrustKotlin]: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/kt/CustomTrust.kt
[CertificatePinningJava]: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CertificatePinning.java
[CertificatePinningKotlin]: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/kt/CertificatePinning.kt
[provider_installer]: https://developer.android.com/training/articles/security-gms-provider
[qualys]: https://www.ssllabs.com/ssltest/
================================================
FILE: docs/features/interceptors.md
================================================
Interceptors
============
Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls. Here's a simple interceptor that logs the outgoing request and the incoming response.
```java
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
```
A call to `chain.proceed(request)` is a critical part of each interceptor’s implementation. This simple-looking method is where all the HTTP work happens, producing a response to satisfy the request. If `chain.proceed(request)` is being called more than once previous response bodies must be closed.
Interceptors can be chained. Suppose you have both a compressing interceptor and a checksumming interceptor: you'll need to decide whether data is compressed and then checksummed, or checksummed and then compressed. OkHttp uses lists to track interceptors, and interceptors are called in order.

### Application Interceptors
Interceptors are registered as either _application_ or _network_ interceptors. We'll use the `LoggingInterceptor` defined above to show the difference.
Register an _application_ interceptor by calling `addInterceptor()` on `OkHttpClient.Builder`:
```java
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Response response = client.newCall(request).execute();
response.body().close();
```
The URL `http://www.publicobject.com/helloworld.txt` redirects to `https://publicobject.com/helloworld.txt`, and OkHttp follows this redirect automatically. Our application interceptor is called **once** and the response returned from `chain.proceed()` has the redirected response:
```
INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example
INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
```
We can see that we were redirected because `response.request().url()` is different from `request.url()`. The two log statements log two different URLs.
### Network Interceptors
Registering a network interceptor is quite similar. Call `addNetworkInterceptor()` instead of `addInterceptor()`:
```java
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Response response = client.newCall(request).execute();
response.body().close();
```
When we run this code, the interceptor runs twice. Once for the initial request to `http://www.publicobject.com/helloworld.txt`, and another for the redirect to `https://publicobject.com/helloworld.txt`.
```
INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt
INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
```
The network requests also contain more data, such as the `Accept-Encoding: gzip` header added by OkHttp to advertise support for response compression. The network interceptor's `Chain` has a non-null `Connection` that can be used to interrogate the IP address and TLS configuration that were used to connect to the webserver.
### Choosing between application and network interceptors
Each interceptor chain has relative merits.
**Application interceptors**
* Don't need to worry about intermediate responses like redirects and retries.
* Are always invoked once, even if the HTTP response is served from the cache.
* Observe the application's original intent. Unconcerned with OkHttp-injected headers like `If-None-Match`.
* Permitted to short-circuit and not call `Chain.proceed()`.
* Permitted to retry and make multiple calls to `Chain.proceed()`.
* Can adjust Call timeouts using withConnectTimeout, withReadTimeout, withWriteTimeout.
**Network Interceptors**
* Able to operate on intermediate responses like redirects and retries.
* Not invoked for cached responses that short-circuit the network.
* Observe the data just as it will be transmitted over the network.
* Access to the `Connection` that carries the request.
### Rewriting Requests
Interceptors can add, remove, or replace request headers. They can also transform the body of those requests that have one. For example, you can use an application interceptor to add request body compression if you're connecting to a webserver known to support it.
```java
/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */
final class GzipRequestInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
return chain.proceed(originalRequest);
}
Request compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method(), gzip(originalRequest.body()))
.build();
return chain.proceed(compressedRequest);
}
private RequestBody gzip(final RequestBody body) {
return new RequestBody() {
@Override public MediaType contentType() {
return body.contentType();
}
@Override public long contentLength() {
return -1; // We don't know the compressed length in advance!
}
@Override public void writeTo(BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
}
```
### Rewriting Responses
Symmetrically, interceptors can rewrite response headers and transform the response body. This is generally more dangerous than rewriting request headers because it may violate the webserver's expectations!
If you're in a tricky situation and prepared to deal with the consequences, rewriting response headers is a powerful way to work around problems. For example, you can fix a server's misconfigured `Cache-Control` response header to enable better response caching:
```java
/** Dangerous interceptor that rewrites the server's cache-control header. */
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.header("Cache-Control", "max-age=60")
.build();
}
};
```
Typically this approach works best when it complements a corresponding fix on the webserver!
================================================
FILE: docs/features/r8_proguard.md
================================================
R8 / ProGuard
=============
If you use OkHttp as a dependency in an Android project which uses R8 as a default compiler you
don't have to do anything. The specific rules are [already bundled][okhttp3_pro] into the JAR which can be
interpreted by R8 automatically.
If you, however, don't use R8 you have to apply the rules from [this file][okhttp3_pro]. You might
also need rules from [Okio][okio] which is a dependency of this library.
[okhttp3_pro]: https://raw.githubusercontent.com/square/okhttp/master/okhttp/okhttp3.pro
[okio]: https://square.github.io/okio/
================================================
FILE: docs/recipes.md
================================================
---
title: Recipes
description: A collection of common/useful code examples for Kotlin and Java
---
# Recipes
We've written some recipes that demonstrate how to solve common problems with OkHttp. Read through them to learn about how everything works together. Cut-and-paste these examples freely; that's what they're for.
### Synchronous Get ([.kt][SynchronousGetKotlin], [.java][SynchronousGetJava])
Download a file, print its headers, and print its response body as a string.
The `string()` method on response body is convenient and efficient for small documents. But if the response body is large (greater than 1 MiB), avoid `string()` because it will load the entire document into memory. In that case, prefer to process the body as a stream.
=== ":material-language-kotlin: Kotlin"
```kotlin
private val client = OkHttpClient()
fun run() {
val request = Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for ((name, value) in response.headers) {
println("$name: $value")
}
println(response.body!!.string())
}
}
```
=== ":material-language-java: Java"
```java
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());
}
}
```
### Asynchronous Get ([.kt][AsynchronousGetKotlin], [.java][AsynchronousGetJava])
Download a file on a worker thread, and get called back when the response is readable. The callback is made after the response headers are ready. Reading the response body may still block. OkHttp doesn't currently offer asynchronous APIs to receive a response body in parts.
=== ":material-language-kotlin: Kotlin"
```kotlin
private val client = OkHttpClient()
fun run() {
val request = Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for ((name, value) in response.headers) {
println("$name: $value")
}
println(response.body!!.string())
}
}
})
}
```
=== ":material-language-java: Java"
```java
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(responseBody.string());
}
}
});
}
```
### Accessing Headers ([.kt][AccessHeadersKotlin], [.java][AccessHeadersJava])
Typically HTTP headers work like a `Map`: each field has one value or none. But some headers permit multiple values, like Guava's [Multimap](https://guava.dev/releases/23.0/api/docs/com/google/common/collect/Multimap.html). For example, it's legal and common for an HTTP response to supply multiple `Vary` headers. OkHttp's APIs attempt to make both cases comfortable.
When writing request headers, use `header(name, value)` to set the only occurrence of `name` to `value`. If there are existing values, they will be removed before the new value is added. Use `addHeader(name, value)` to add a header without removing the headers already present.
When reading response a header, use `header(name)` to return the _last_ occurrence of the named value. Usually this is also the only occurrence! If no value is present, `header(name)` will return null. To read all of a field's values as a list, use `headers(name)`.
To visit all headers, use the `Headers` class which supports access by index.
=== ":material-language-kotlin: Kotlin"
```kotlin
private val client = OkHttpClient()
fun run() {
val request = Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
println("Server: ${response.header("Server")}")
println("Date: ${response.header("Date")}")
println("Vary: ${response.headers("Vary")}")
}
}
```
=== ":material-language-java: Java"
```java
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println("Server: " + response.header("Server"));
System.out.println("Date: " + response.header("Date"));
System.out.println("Vary: " + response.headers("Vary"));
}
}
```
### Posting a String ([.kt][PostStringKotlin], [.java][PostStringJava])
Use an HTTP POST to send a request body to a service. This example posts a markdown document to a web service that renders markdown as HTML. Because the entire request body is in memory simultaneously, avoid posting large (greater than 1 MiB) documents using this API.
=== ":material-language-kotlin: Kotlin"
```kotlin
private val client = OkHttpClient()
fun run() {
val postBody = """
|Releases
|--------
|
| * _1.0_ May 6, 2013
| * _1.1_ June 15, 2013
| * _1.2_ August 11, 2013
|""".trimMargin()
val request = Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(postBody.toRequestBody(MEDIA_TYPE_MARKDOWN))
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
println(response.body!!.string())
}
}
companion object {
val MEDIA_TYPE_MARKDOWN = "text/x-markdown; charset=utf-8".toMediaType()
}
```
=== ":material-language-java: Java"
```java
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
String postBody = ""
+ "Releases\n"
+ "--------\n"
+ "\n"
+ " * _1.0_ May 6, 2013\n"
+ " * _1.1_ June 15, 2013\n"
+ " * _1.2_ August 11, 2013\n";
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
```
### Post Streaming ([.kt][PostStreamingKotlin], [.java][PostStreamingJava])
Here we `POST` a request body as a stream. The content of this request body is being generated as it's being written. This example streams directly into the [Okio](https://github.com/square/okio) buffered sink. Your programs may prefer an `OutputStream`, which you can get from `BufferedSink.outputStream()`.
=== ":material-language-kotlin: Kotlin"
```kotlin
private val client = OkHttpClient()
fun run() {
val requestBody = object : RequestBody() {
override fun contentType() = MEDIA_TYPE_MARKDOWN
override fun writeTo(sink: BufferedSink) {
sink.writeUtf8("Numbers\n")
sink.writeUtf8("-------\n")
for (i in 2..997) {
sink.writeUtf8(String.format(" * $i = ${factor(i)}\n"))
}
}
private fun factor(n: Int): String {
for (i in 2 until n) {
val x = n / i
if (x * i == n) return "${factor(x)} × $i"
}
return n.toString()
}
}
val request = Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
println(response.body!!.string())
}
}
companion object {
val MEDIA_TYPE_MARKDOWN = "text/x-markdown; charset=utf-8".toMediaType()
}
```
=== ":material-language-java: Java"
```java
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody requestBody = new RequestBody() {
@Override public MediaType contentType() {
return MEDIA_TYPE_MARKDOWN;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
sink.writeUtf8("Numbers\n");
sink.writeUtf8("-------\n");
for (int i = 2; i <= 997; i++) {
sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
}
}
private String factor(int n) {
for (int i = 2; i < n; i++) {
int x = n / i;
if (x * i == n) return factor(x) + " × " + i;
}
return Integer.toString(n);
}
};
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
```
### Posting a File ([.kt][PostFileKotlin], [.java][PostFileJava])
It's easy to use a file as a request body.
=== ":material-language-kotlin: Kotlin"
```kotlin
private val client = OkHttpClient()
fun run() {
val file = File("README.md")
val request = Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(file.asRequestBody(MEDIA_TYPE_MARKDOWN))
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
println(response.body!!.string())
}
}
companion object {
val MEDIA_TYPE_MARKDOWN = "text/x-markdown; charset=utf-8".toMediaType()
}
```
=== ":material-language-java: Java"
```java
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
File file = new File("README.md");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
```
### Posting form parameters ([.kt][PostFormKotlin], [.java][PostFormJava])
Use `FormBody.Builder` to build a request body that works like an HTML `