Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android-test-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ android {

dependencies {
implementation(libs.playservices.safetynet)
implementation(projects.okhttp)
"friendsImplementation"(projects.okhttp)
implementation(libs.androidx.activity)

androidTestImplementation(libs.androidx.junit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,26 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import okhttp3.Call
import okhttp3.Callback
import okhttp3.ConnectionPool
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.android.tracing.AndroidxTracingConnectionListener
import okhttp3.android.tracing.AndroidxTracingInterceptor
import okhttp3.internal.platform.AndroidPlatform
import okio.IOException

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val client = OkHttpClient()
val client =
OkHttpClient
.Builder()
.connectionPool(ConnectionPool(connectionListener = AndroidxTracingConnectionListener()))
.addNetworkInterceptor(AndroidxTracingInterceptor())
.build()

// Ensure we are compiling against the right variant
println(AndroidPlatform.isSupported)
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[versions]
androidx-tracing = "1.3.0"
# 7.0.0 is JDK 17+ https://github.com/bndtools/bnd/wiki/Changes-in-7.0.0
biz-aQute-bnd = "7.1.0"
checkStyle = "10.26.1"
Expand Down Expand Up @@ -29,6 +30,7 @@ androidx-junit = "androidx.test.ext:junit:1.2.1"
androidx-lint-gradle = { module = "androidx.lint:lint-gradle", version.ref = "lintGradle" }
androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "startupRuntime" }
androidx-test-runner = "androidx.test:runner:1.6.2"
androidx-tracing-ktx = { module = "androidx.tracing:tracing-ktx", version.ref = "androidx-tracing" }
animalsniffer-annotations = "org.codehaus.mojo:animal-sniffer-annotations:1.24"
aqute-resolve = { module = "biz.aQute.bnd:biz.aQute.resolve", version.ref = "biz-aQute-bnd" }
assertk = "com.willowtreeapps.assertk:assertk:0.28.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,13 @@ internal open class RecordingConnectionListener(
}

override fun connectStart(
connectionId: Long,
route: Route,
call: Call,
) = logEvent(ConnectionEvent.ConnectStart(System.nanoTime(), route, call))

override fun connectFailed(
connectionId: Long,
route: Route,
call: Call,
failure: IOException,
Expand Down
1 change: 1 addition & 0 deletions okhttp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ kotlin {
compileOnly(libs.conscrypt.openjdk)
implementation(libs.androidx.annotation)
implementation(libs.androidx.startup.runtime)
implementation(libs.androidx.tracing.ktx)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package okhttp3.android.tracing

import androidx.tracing.Trace
import okhttp3.Call
import okhttp3.Connection
import okhttp3.Route
import okhttp3.internal.connection.ConnectionListener
import okio.IOException

/**
* Tracing implementation of ConnectionListener that marks the lifetime of each connection
* in Perfetto traces.
*/
class AndroidxTracingConnectionListener(
private val delegate: ConnectionListener = NONE,
val traceLabel: (Route) -> String = { it.defaultTracingLabel },
) : ConnectionListener() {
override fun connectStart(
connectionId: Long,
route: Route,
call: Call,
) {
Trace.beginAsyncSection(labelForTrace(route), connectionId.toInt())
delegate.connectStart(connectionId, route, call)
}

override fun connectFailed(
connectionId: Long,
route: Route,
call: Call,
failure: IOException,
) {
Trace.endAsyncSection(labelForTrace(route), connectionId.toInt())
delegate.connectFailed(connectionId, route, call, failure)
}

override fun connectEnd(
connection: Connection,
route: Route,
call: Call,
) {
delegate.connectEnd(connection, route, call)
}

override fun connectionClosed(connection: Connection) {
Trace.endAsyncSection(labelForTrace(connection.route()), connection.id.toInt())
delegate.connectionClosed(connection)
}

private fun labelForTrace(route: Route): String = traceLabel(route).take(AndroidxTracingInterceptor.Companion.MAX_TRACE_LABEL_LENGTH)

override fun connectionAcquired(
connection: Connection,
call: Call,
) {
delegate.connectionAcquired(connection, call)
}

override fun connectionReleased(
connection: Connection,
call: Call,
) {
delegate.connectionReleased(connection, call)
}

override fun noNewExchanges(connection: Connection) {
delegate.noNewExchanges(connection)
}

companion object {
val Route.defaultTracingLabel: String
get() = this.address.url.host
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package okhttp3.android.tracing

import androidx.tracing.trace
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response

/**
* Tracing implementation of Interceptor that marks each Call in a Perfetto
* trace. Typically used as a network interceptor.
*/
class AndroidxTracingInterceptor(
val traceLabel: (Request) -> String = { it.defaultTracingLabel },
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response =
trace(traceLabel(chain.request()).take(MAX_TRACE_LABEL_LENGTH)) {
chain.proceed(chain.request())
}

companion object {
internal const val MAX_TRACE_LABEL_LENGTH = 127

val Request.defaultTracingLabel: String
get() {
return url.encodedPath
}
}
}
4 changes: 4 additions & 0 deletions okhttp/src/commonJvmAndroid/kotlin/okhttp3/Connection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ import java.net.Socket
* been found. But only complete the stream once its data stream has been exhausted.
*/
interface Connection {
/** Unique id of this connection, assigned at the time of the attempt. */
val id: Long
get() = 0L

/** Returns the route used by this connection. */
fun route(): Route

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class ConnectionPool internal constructor(
)

// Internal until we promote ConnectionListener to be a public API.
internal constructor(
constructor(
maxIdleConnections: Int = 5,
keepAliveDuration: Long = 5,
timeUnit: TimeUnit = TimeUnit.MINUTES,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,22 @@ internal class CallConnectionUser(
call.client.routeDatabase.connected(route)
}

override fun connectStart(route: Route) {
override fun connectStart(
connectionId: Long,
route: Route,
) {
eventListener.connectStart(call, route.socketAddress, route.proxy)
poolConnectionListener.connectStart(route, call)
poolConnectionListener.connectStart(connectionId, route, call)
}

override fun connectFailed(
connectionId: Long,
route: Route,
protocol: Protocol?,
e: IOException,
) {
eventListener.connectFailed(call, route.socketAddress, route.proxy, null, e)
poolConnectionListener.connectFailed(route, call, e)
poolConnectionListener.connectFailed(connectionId, route, call, e)
}

override fun secureConnectStart() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import java.net.Socket
import java.net.UnknownServiceException
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicLong
import javax.net.ssl.SSLPeerUnverifiedException
import javax.net.ssl.SSLSocket
import okhttp3.CertificatePinner
Expand Down Expand Up @@ -79,6 +80,8 @@ class ConnectPlan(
internal val isTlsFallback: Boolean,
) : RoutePlanner.Plan,
ExchangeCodec.Carrier {
private val id = idGenerator.incrementAndGet()

/** True if this connect was canceled; typically because it lost a race. */
@Volatile private var canceled = false

Expand Down Expand Up @@ -135,7 +138,7 @@ class ConnectPlan(
// Tell the call about the connecting call so async cancels work.
user.addPlanToCancel(this)
try {
user.connectStart(route)
user.connectStart(id, route)

connectSocket()
success = true
Expand All @@ -149,7 +152,7 @@ class ConnectPlan(
e,
)
}
user.connectFailed(route, null, e)
user.connectFailed(id, route, null, e)
return ConnectResult(plan = this, throwable = e)
} finally {
user.removePlanToCancel(this)
Expand Down Expand Up @@ -231,6 +234,7 @@ class ConnectPlan(
sink = sink,
pingIntervalMillis = pingIntervalMillis,
connectionListener = connectionPool.connectionListener,
id = id,
)
this.connection = connection
connection.start()
Expand All @@ -240,7 +244,7 @@ class ConnectPlan(
success = true
return ConnectResult(plan = this)
} catch (e: IOException) {
user.connectFailed(route, null, e)
user.connectFailed(id, route, null, e)

if (!retryOnConnectionFailure || !retryTlsHandshake(e)) {
retryTlsConnection = null
Expand Down Expand Up @@ -333,7 +337,7 @@ class ConnectPlan(
ProtocolException(
"Too many tunnel connections attempted: $MAX_TUNNEL_ATTEMPTS",
)
user.connectFailed(route, null, failure)
user.connectFailed(id, route, null, failure)
return ConnectResult(plan = this, throwable = failure)
}
}
Expand Down Expand Up @@ -565,5 +569,7 @@ class ConnectPlan(
companion object {
private const val NPE_THROW_WITH_NULL = "throw with null exception"
private const val MAX_TUNNEL_ATTEMPTS = 21

private val idGenerator = AtomicLong(0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ import okio.IOException
* attempt to mutate the event parameters, or be reentrant back into the client.
* Any IO - writing to files or network should be done asynchronously.
*/
internal abstract class ConnectionListener {
abstract class ConnectionListener {
/**
* Invoked as soon as a call causes a connection to be started.
*/
open fun connectStart(
connectionId: Long,
route: Route,
call: Call,
) {}
Expand All @@ -40,6 +41,7 @@ internal abstract class ConnectionListener {
* Invoked when a connection fails to be established.
*/
open fun connectFailed(
connectionId: Long,
route: Route,
call: Call,
failure: IOException,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ interface ConnectionUser {

fun updateRouteDatabaseAfterSuccess(route: Route)

fun connectStart(route: Route)
fun connectStart(
connectionId: Long,
route: Route,
)

fun secureConnectStart()

Expand All @@ -52,6 +55,7 @@ interface ConnectionUser {
)

fun connectFailed(
connectionId: Long,
route: Route,
protocol: Protocol?,
e: IOException,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ object PoolConnectionUser : ConnectionUser {
override fun updateRouteDatabaseAfterSuccess(route: Route) {
}

override fun connectStart(route: Route) {
override fun connectStart(
connectionId: Long,
route: Route,
) {
}

override fun secureConnectStart() {
Expand All @@ -61,6 +64,7 @@ object PoolConnectionUser : ConnectionUser {
}

override fun connectFailed(
connectionId: Long,
route: Route,
protocol: Protocol?,
e: IOException,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class RealConnection internal constructor(
private val sink: BufferedSink,
private val pingIntervalMillis: Int,
internal val connectionListener: ConnectionListener,
override val id: Long,
) : Http2Connection.Listener(),
Connection,
ExchangeCodec.Carrier,
Expand Down Expand Up @@ -502,6 +503,7 @@ class RealConnection internal constructor(
}.buffer(),
pingIntervalMillis = 0,
ConnectionListener.NONE,
0L,
)
result.idleAtNs = idleAtNs
return result
Expand Down
Loading