Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile against JDK11 API, run tests down to JDK8 #5429

Merged
merged 18 commits into from
Sep 21, 2019
20 changes: 13 additions & 7 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ commands:
parameters:
platform:
type: string
testjdk:
type: string
default: ""
steps:
- restore_cache:
keys:
Expand All @@ -17,7 +20,7 @@ commands:
name: Run tests
command: ./gradlew --build-cache --parallel --continue test
environment:
GRADLE_OPTS: -Dorg.gradle.daemon=false -Dokhttp.platform=<< parameters.platform >> -Dorg.gradle.workers.max=3 -Xmx1G
GRADLE_OPTS: -Dorg.gradle.daemon=false -Dokhttp.platform=<< parameters.platform >> -Dorg.gradle.workers.max=3 -Xmx1G << parameters.testjdk >>

- save_cache:
paths:
Expand Down Expand Up @@ -79,9 +82,9 @@ jobs:
# Under normal usage, saves compiled results from master at least once a day
key: v4-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ epoch }}

checkjdk8:
checkjdk11:
docker:
- image: circleci/openjdk:8u222-jdk-stretch
- image: circleci/openjdk:11.0.4-jdk-stretch

environment:
JVM_OPTS: -Xmx1g
Expand Down Expand Up @@ -114,7 +117,7 @@ jobs:

testjdk8:
docker:
- image: circleci/openjdk:8u222-jdk-stretch
- image: circleci/openjdk:11.0.4-jdk-stretch

environment:
JVM_OPTS: -Xmx1g
Expand All @@ -125,10 +128,11 @@ jobs:

- runtests:
platform: jdk8
testjdk: -Dtest.java.home=/usr/lib/jvm/java-8-openjdk-amd64

testjdk8alpn:
docker:
- image: circleci/openjdk:8u222-jdk-stretch
- image: circleci/openjdk:11.0.4-jdk-stretch

environment:
JVM_OPTS: -Xmx1g
Expand All @@ -139,6 +143,7 @@ jobs:

- runtests:
platform: jdk8alpn
testjdk: -Dtest.java.home=/usr/lib/jvm/java-8-openjdk-amd64 -Dalpn.boot.version=8.1.13.v20181017

testopenjsse:
docker:
Expand All @@ -153,6 +158,7 @@ jobs:

- runtests:
platform: openjsse
testjdk: -Dtest.java.home=/usr/lib/jvm/java-8-openjdk-amd64

testjdk11:
docker:
Expand Down Expand Up @@ -205,7 +211,7 @@ workflows:
filters:
branches:
only: master
- checkjdk8:
- checkjdk11:
filters:
branches:
ignore:
Expand Down Expand Up @@ -245,7 +251,7 @@ workflows:
only: master
jobs:
- compile
- checkjdk8:
- checkjdk11:
requires:
- compile
- testjdk8:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ import java.net.InetAddress
import java.net.UnknownHostException
import javax.net.ssl.SSLPeerUnverifiedException
import javax.net.ssl.SSLSocket
import okhttp3.internal.platform.Platform
import okhttp3.internal.platform.AndroidPlatform
import okhttp3.internal.platform.AndroidQPlatform

/**
* Run with "./gradlew :android-test:connectedCheck" and make sure ANDROID_SDK_ROOT is set.
Expand All @@ -67,15 +70,23 @@ class OkHttpTest {

@Before
fun createClient() {
client = OkHttpClient.Builder()
.build()
client = OkHttpClient.Builder().build()
}

@After
fun cleanup() {
client.dispatcher.executorService.shutdownNow()
}

@Test
fun testPlatform() {
if (Build.VERSION.SDK_INT >= 29) {
yschimke marked this conversation as resolved.
Show resolved Hide resolved
assertTrue(Platform.get() is AndroidQPlatform)
} else {
assertTrue(Platform.get() is AndroidPlatform)
}
}

@Test
fun testRequest() {
assumeNetwork()
Expand Down
14 changes: 12 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import net.ltgt.gradle.errorprone.CheckSeverity

buildscript {
ext.versions = [
'android': '4.1.1.4',
'animalSniffer': '1.17',
'assertj': '3.11.0',
'bouncycastle': '1.62',
Expand All @@ -25,7 +24,7 @@ buildscript {

ext.deps = [
'picocli': "info.picocli:picocli:${versions.picocli}",
'android': "com.google.android:android:${versions.android}",
'android': "org.robolectric:android-all:9-robolectric-4913185-2",
yschimke marked this conversation as resolved.
Show resolved Hide resolved
'animalSniffer': "org.codehaus.mojo:animal-sniffer-annotations:${versions.animalSniffer}",
'assertj': "org.assertj:assertj-core:${versions.assertj}",
'bouncycastle': "org.bouncycastle:bcprov-jdk15on:${versions.bouncycastle}",
Expand Down Expand Up @@ -132,11 +131,16 @@ subprojects { project ->
}

def platform = System.getProperty("okhttp.platform", "jdk8")
def platformJavaHome = System.getProperty('test.java.home')

test {
jvmArgs += "-Dlistener=okhttp3.testing.InstallUncaughtExceptionHandlerListener"
jvmArgs += "-Dokhttp.platform=$platform"

if (platformJavaHome != null) {
executable = "$platformJavaHome/bin/java"
}

maxParallelForks Runtime.runtime.availableProcessors() * 2
testLogging {
exceptionFormat = 'full'
Expand Down Expand Up @@ -211,6 +215,12 @@ tasks.wrapper {
* https://github.com/xjdr/xio/blob/master/alpn-boot.gradle
*/
def alpnBootVersion() {
def version = System.getProperty('alpn.boot.version')

if (version != null) {
return version
}

def javaVersion = System.getProperty("java.version")
def patchVersionMatcher = (javaVersion =~ /1\.8\.0_(\d+)(-.*)?/)
if (!patchVersionMatcher.find()) return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ class MockWebServer : ExternalResource(), Closeable {
openClientSockets.add(socket)

if (protocolNegotiationEnabled) {
Platform.get().configureTlsExtensions(sslSocket, null, protocols)
Platform.get().configureTlsExtensions(sslSocket, protocols)
}

sslSocket.startHandshake()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private SSLSocket doSsl(Socket socket) throws IOException {
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
sslSocket.setUseClientMode(false);
Platform.get().configureTlsExtensions(sslSocket, null,
Platform.get().configureTlsExtensions(sslSocket,
Collections.singletonList(Protocol.HTTP_2));
sslSocket.startHandshake();
return sslSocket;
Expand Down
6 changes: 5 additions & 1 deletion okhttp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ task copyJavaTemplates(type: Copy) {
dependencies {
api deps.okio
api deps.kotlinStdlib
compileOnly deps.android
compileOnly deps.conscrypt
compileOnly deps.openjsse
compileOnly deps.android
compileOnly deps.jsr305
compileOnly deps.animalSniffer

Expand All @@ -50,6 +50,10 @@ afterEvaluate { project ->
}
}

animalsniffer {
ignore 'javax.net.ssl.SSLParameters', 'javax.net.ssl.SSLSocket'
}

task japicmp(type: me.champeau.gradle.japicmp.JapicmpTask, dependsOn: 'jar') {
oldClasspath = files(baselineJar(project, baselineVersion))
newClasspath = files(jar.archivePath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ class RealConnection(
// Configure the socket's ciphers, TLS versions, and extensions.
val connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket)
if (connectionSpec.supportsTlsExtensions) {
Platform.get().configureTlsExtensions(sslSocket, address.url.host, address.protocols)
Platform.get().configureTlsExtensions(sslSocket, address.protocols)
}

// Force handshake. This can throw!
Expand Down
50 changes: 13 additions & 37 deletions okhttp/src/main/java/okhttp3/internal/platform/AndroidPlatform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package okhttp3.internal.platform

import android.os.Build
import okhttp3.Protocol
import okhttp3.internal.platform.android.AndroidCertificateChainCleaner
import okhttp3.internal.platform.android.CloseGuard
import okhttp3.internal.platform.android.ConscryptSocketAdapter
import okhttp3.internal.platform.android.DeferredSocketAdapter
Expand All @@ -30,10 +31,8 @@ import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import java.net.InetSocketAddress
import java.net.Socket
import java.security.cert.Certificate
import java.security.cert.TrustAnchor
import java.security.cert.X509Certificate
import javax.net.ssl.SSLPeerUnverifiedException
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager
Expand Down Expand Up @@ -73,12 +72,11 @@ class AndroidPlatform : Platform() {

override fun configureTlsExtensions(
sslSocket: SSLSocket,
hostname: String?,
protocols: List<Protocol>
protocols: List<@JvmSuppressWildcards Protocol>
) {
// No TLS extensions if the socket class is custom.
socketAdapters.find { it.matchesSocket(sslSocket) }
?.configureTlsExtensions(sslSocket, hostname, protocols)
?.configureTlsExtensions(sslSocket, protocols)
}

override fun getSelectedProtocol(sslSocket: SSLSocket) =
Expand Down Expand Up @@ -168,37 +166,6 @@ class AndroidPlatform : Platform() {
super.buildTrustRootIndex(trustManager)
}

/**
* X509TrustManagerExtensions was added to Android in API 17 (Android 4.2, released in late 2012).
* This is the best way to get a clean chain on Android because it uses the same code as the TLS
* handshake.
*/
internal class AndroidCertificateChainCleaner(
private val x509TrustManagerExtensions: Any,
private val checkServerTrusted: Method
) : CertificateChainCleaner() {

@Suppress("UNCHECKED_CAST")
@Throws(SSLPeerUnverifiedException::class)
override // Reflection on List<Certificate>.
fun clean(chain: List<Certificate>, hostname: String): List<Certificate> = try {
val certificates = (chain as List<X509Certificate>).toTypedArray()
checkServerTrusted.invoke(
x509TrustManagerExtensions, certificates, "RSA", hostname) as List<Certificate>
} catch (e: InvocationTargetException) {
val exception = SSLPeerUnverifiedException(e.message)
exception.initCause(e)
throw exception
} catch (e: IllegalAccessException) {
throw AssertionError(e)
}

override fun equals(other: Any?): Boolean =
other is AndroidCertificateChainCleaner // All instances are equivalent.

override fun hashCode(): Int = 0
}

/**
* A trust manager for Android applications that customize the trust manager.
*
Expand All @@ -224,12 +191,21 @@ class AndroidPlatform : Platform() {
}

companion object {
val isAndroid: Boolean = try {
// Trigger an early exception over a fatal error, prefer a RuntimeException over Error.
Class.forName("com.android.org.conscrypt.OpenSSLSocketImpl")
true
} catch (_: ClassNotFoundException) {
false
}

val isSupported: Boolean = try {
// Trigger an early exception over a fatal error, prefer a RuntimeException over Error.
Class.forName("com.android.org.conscrypt.OpenSSLSocketImpl")

// Fail Fast
check(Build.VERSION.SDK_INT >= 21) { "Expected Android API level 21+ but was ${Build.VERSION.SDK_INT}" }
check(
Build.VERSION.SDK_INT >= 21) { "Expected Android API level 21+ but was ${Build.VERSION.SDK_INT}" }

true
} catch (_: ClassNotFoundException) {
Expand Down
50 changes: 50 additions & 0 deletions okhttp/src/main/java/okhttp3/internal/platform/AndroidQPlatform.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2016 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.internal.platform

import android.os.Build
import android.security.NetworkSecurityPolicy
import okhttp3.internal.platform.AndroidPlatform.Companion.isAndroid
import okhttp3.internal.platform.android.AndroidQCertificateChainCleaner
import okhttp3.internal.tls.CertificateChainCleaner
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Socket
import javax.net.ssl.X509TrustManager

/** Android 29+. */
class AndroidQPlatform : Jdk9Platform() {
@Throws(IOException::class)
override fun connectSocket(
socket: Socket,
address: InetSocketAddress,
connectTimeout: Int
) {
socket.connect(address, connectTimeout)
}

override fun isCleartextTrafficPermitted(hostname: String): Boolean =
NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(hostname)

override fun buildCertificateChainCleaner(trustManager: X509TrustManager): CertificateChainCleaner =
AndroidQCertificateChainCleaner(trustManager)

companion object {
val isSupported: Boolean = isAndroid && Build.VERSION.SDK_INT >= 29

fun buildIfSupported(): Platform? = if (isSupported) AndroidQPlatform() else null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,17 @@ class ConscryptPlatform private constructor() : Platform() {

override fun configureTlsExtensions(
sslSocket: SSLSocket,
hostname: String?,
protocols: List<Protocol>
protocols: List<@JvmSuppressWildcards Protocol>
) {
if (Conscrypt.isConscrypt(sslSocket)) {
// Enable SNI and session tickets.
if (hostname != null) {
Conscrypt.setUseSessionTickets(sslSocket, true)
Conscrypt.setHostname(sslSocket, hostname)
}
// Enable session tickets.
Conscrypt.setUseSessionTickets(sslSocket, true)

// Enable ALPN.
val names = alpnProtocolNames(protocols)
Conscrypt.setApplicationProtocols(sslSocket, names.toTypedArray())
} else {
super.configureTlsExtensions(sslSocket, hostname, protocols)
super.configureTlsExtensions(sslSocket, protocols)
}
}

Expand Down
Loading