mirror of
https://github.com/square/okhttp.git
synced 2026-01-12 10:23:16 +03:00
Call.addEventListener (#9181)
* Call.addEventListener Use this to install listeners after-the-fact, such as in Interceptors or other EventListeners. * apiDump * Make eventListener updates atomic * Fix
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* A special [EventListener] for testing the mechanics of event listeners.
|
||||
*
|
||||
* Each instance processes a single event on [call], and then adds a successor [EventListenerRelay]
|
||||
* on the same [call] to process the next event.
|
||||
*
|
||||
* By forcing the list of listeners to change after every event, we can detect if buggy code caches
|
||||
* a stale [EventListener] in a field or local variable.
|
||||
*/
|
||||
class EventListenerRelay(
|
||||
val call: Call,
|
||||
val eventRecorder: EventRecorder,
|
||||
) {
|
||||
private val eventListenerAdapter =
|
||||
EventListenerAdapter()
|
||||
.apply {
|
||||
listeners += ::onEvent
|
||||
}
|
||||
|
||||
val eventListener: EventListener
|
||||
get() = eventListenerAdapter
|
||||
|
||||
var eventCount = 0
|
||||
|
||||
private fun onEvent(callEvent: CallEvent) {
|
||||
if (eventCount++ == 0) {
|
||||
eventRecorder.logEvent(callEvent)
|
||||
val next = EventListenerRelay(call, eventRecorder)
|
||||
call.addEventListener(next.eventListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,7 @@ open class EventRecorder(
|
||||
}
|
||||
}
|
||||
|
||||
private fun logEvent(e: CallEvent) {
|
||||
internal fun logEvent(e: CallEvent) {
|
||||
for (lock in forbiddenLocks) {
|
||||
assertThat(Thread.holdsLock(lock), lock.toString()).isFalse()
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ open class FailingCall : Call {
|
||||
|
||||
override fun timeout(): Timeout = error("unexpected")
|
||||
|
||||
override fun addEventListener(eventListener: EventListener) = error("unexpected")
|
||||
|
||||
override fun <T : Any> tag(type: KClass<T>): T? = error("unexpected")
|
||||
|
||||
override fun <T> tag(type: Class<out T>): T? = error("unexpected")
|
||||
|
||||
@@ -119,6 +119,7 @@ public final class okhttp3/CacheControl$Companion {
|
||||
}
|
||||
|
||||
public abstract interface class okhttp3/Call : java/lang/Cloneable {
|
||||
public abstract fun addEventListener (Lokhttp3/EventListener;)V
|
||||
public abstract fun cancel ()V
|
||||
public abstract fun clone ()Lokhttp3/Call;
|
||||
public abstract fun enqueue (Lokhttp3/Callback;)V
|
||||
|
||||
@@ -119,6 +119,7 @@ public final class okhttp3/CacheControl$Companion {
|
||||
}
|
||||
|
||||
public abstract interface class okhttp3/Call : java/lang/Cloneable {
|
||||
public abstract fun addEventListener (Lokhttp3/EventListener;)V
|
||||
public abstract fun cancel ()V
|
||||
public abstract fun clone ()Lokhttp3/Call;
|
||||
public abstract fun enqueue (Lokhttp3/Callback;)V
|
||||
|
||||
@@ -88,6 +88,19 @@ interface Call : Cloneable {
|
||||
*/
|
||||
fun timeout(): Timeout
|
||||
|
||||
/**
|
||||
* Configure this call to publish all future events to [eventListener], in addition to the
|
||||
* listeners configured by [OkHttpClient.Builder.eventListener] and other calls to this function.
|
||||
*
|
||||
* If this call is later [cloned][clone], [eventListener] will not be notified of its events.
|
||||
*
|
||||
* There is no mechanism to remove an event listener. Implementations should instead ignore events
|
||||
* that they are not interested in.
|
||||
*
|
||||
* @see EventListener for semantics and restrictions on listener implementations.
|
||||
*/
|
||||
fun addEventListener(eventListener: EventListener)
|
||||
|
||||
/**
|
||||
* Returns the tag attached with [type] as a key, or null if no tag is attached with that key.
|
||||
*
|
||||
@@ -161,6 +174,9 @@ interface Call : Cloneable {
|
||||
* copy.tag(MyTag.class, () -> myTag);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* If any event listeners were installed on this call with [addEventListener], they will not be
|
||||
* installed on this copy.
|
||||
*/
|
||||
public override fun clone(): Call
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.net.HttpURLConnection.HTTP_GATEWAY_TIMEOUT
|
||||
import java.net.HttpURLConnection.HTTP_NOT_MODIFIED
|
||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
import okhttp3.Cache
|
||||
import okhttp3.EventListener
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Protocol
|
||||
@@ -42,11 +41,11 @@ import okio.buffer
|
||||
|
||||
/** Serves requests from the cache and writes responses to the cache. */
|
||||
class CacheInterceptor(
|
||||
internal val call: RealCall,
|
||||
internal val cache: Cache?,
|
||||
) : Interceptor {
|
||||
@Throws(IOException::class)
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val call = chain.call()
|
||||
val cacheCandidate = cache?.get(chain.request().requestForCache())
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
@@ -56,7 +55,6 @@ class CacheInterceptor(
|
||||
val cacheResponse = strategy.cacheResponse
|
||||
|
||||
cache?.trackResponse(strategy)
|
||||
val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
|
||||
|
||||
if (cacheCandidate != null && cacheResponse == null) {
|
||||
// The cache candidate wasn't applicable. Close it.
|
||||
@@ -75,7 +73,7 @@ class CacheInterceptor(
|
||||
.receivedResponseAtMillis(System.currentTimeMillis())
|
||||
.build()
|
||||
.also {
|
||||
listener.satisfactionFailure(call, it)
|
||||
call.eventListener.satisfactionFailure(call, it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,14 +84,14 @@ class CacheInterceptor(
|
||||
.cacheResponse(cacheResponse.stripBody())
|
||||
.build()
|
||||
.also {
|
||||
listener.cacheHit(call, it)
|
||||
call.eventListener.cacheHit(call, it)
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheResponse != null) {
|
||||
listener.cacheConditionalHit(call, cacheResponse)
|
||||
call.eventListener.cacheConditionalHit(call, cacheResponse)
|
||||
} else if (cache != null) {
|
||||
listener.cacheMiss(call)
|
||||
call.eventListener.cacheMiss(call)
|
||||
}
|
||||
|
||||
var networkResponse: Response? = null
|
||||
@@ -126,7 +124,7 @@ class CacheInterceptor(
|
||||
cache!!.trackConditionalCacheHit()
|
||||
cache.update(cacheResponse, response)
|
||||
return response.also {
|
||||
listener.cacheHit(call, it)
|
||||
call.eventListener.cacheHit(call, it)
|
||||
}
|
||||
} else {
|
||||
cacheResponse.body.closeQuietly()
|
||||
@@ -149,7 +147,7 @@ class CacheInterceptor(
|
||||
return cacheWritingResponse(cacheRequest, response).also {
|
||||
if (cacheResponse != null) {
|
||||
// This will log a conditional cache miss only.
|
||||
listener.cacheMiss(call)
|
||||
call.eventListener.cacheMiss(call)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ import okio.buffer
|
||||
*/
|
||||
class Exchange(
|
||||
internal val call: RealCall,
|
||||
internal val eventListener: EventListener,
|
||||
internal val finder: ExchangeFinder,
|
||||
private val codec: ExchangeCodec,
|
||||
) {
|
||||
@@ -59,11 +58,11 @@ class Exchange(
|
||||
@Throws(IOException::class)
|
||||
fun writeRequestHeaders(request: Request) {
|
||||
try {
|
||||
eventListener.requestHeadersStart(call)
|
||||
call.eventListener.requestHeadersStart(call)
|
||||
codec.writeRequestHeaders(request)
|
||||
eventListener.requestHeadersEnd(call, request)
|
||||
call.eventListener.requestHeadersEnd(call, request)
|
||||
} catch (e: IOException) {
|
||||
eventListener.requestFailed(call, e)
|
||||
call.eventListener.requestFailed(call, e)
|
||||
trackFailure(e)
|
||||
throw e
|
||||
}
|
||||
@@ -76,7 +75,7 @@ class Exchange(
|
||||
): Sink {
|
||||
this.isDuplex = duplex
|
||||
val contentLength = request.body!!.contentLength()
|
||||
eventListener.requestBodyStart(call)
|
||||
call.eventListener.requestBodyStart(call)
|
||||
val rawRequestBody = codec.createRequestBody(request, contentLength)
|
||||
return RequestBodySink(
|
||||
delegate = rawRequestBody,
|
||||
@@ -90,7 +89,7 @@ class Exchange(
|
||||
try {
|
||||
codec.flushRequest()
|
||||
} catch (e: IOException) {
|
||||
eventListener.requestFailed(call, e)
|
||||
call.eventListener.requestFailed(call, e)
|
||||
trackFailure(e)
|
||||
throw e
|
||||
}
|
||||
@@ -101,14 +100,14 @@ class Exchange(
|
||||
try {
|
||||
codec.finishRequest()
|
||||
} catch (e: IOException) {
|
||||
eventListener.requestFailed(call, e)
|
||||
call.eventListener.requestFailed(call, e)
|
||||
trackFailure(e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
fun responseHeadersStart() {
|
||||
eventListener.responseHeadersStart(call)
|
||||
call.eventListener.responseHeadersStart(call)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
@@ -118,14 +117,14 @@ class Exchange(
|
||||
result?.initExchange(this)
|
||||
return result
|
||||
} catch (e: IOException) {
|
||||
eventListener.responseFailed(call, e)
|
||||
call.eventListener.responseFailed(call, e)
|
||||
trackFailure(e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
fun responseHeadersEnd(response: Response) {
|
||||
eventListener.responseHeadersEnd(call, response)
|
||||
call.eventListener.responseHeadersEnd(call, response)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
@@ -142,7 +141,7 @@ class Exchange(
|
||||
)
|
||||
return RealResponseBody(contentType, contentLength, source.buffer())
|
||||
} catch (e: IOException) {
|
||||
eventListener.responseFailed(call, e)
|
||||
call.eventListener.responseFailed(call, e)
|
||||
trackFailure(e)
|
||||
throw e
|
||||
}
|
||||
@@ -217,16 +216,16 @@ class Exchange(
|
||||
}
|
||||
if (requestDone) {
|
||||
if (e != null) {
|
||||
eventListener.requestFailed(call, e)
|
||||
call.eventListener.requestFailed(call, e)
|
||||
} else {
|
||||
eventListener.requestBodyEnd(call, bytesRead)
|
||||
call.eventListener.requestBodyEnd(call, bytesRead)
|
||||
}
|
||||
}
|
||||
if (responseDone) {
|
||||
if (e != null) {
|
||||
eventListener.responseFailed(call, e)
|
||||
call.eventListener.responseFailed(call, e)
|
||||
} else {
|
||||
eventListener.responseBodyEnd(call, bytesRead)
|
||||
call.eventListener.responseBodyEnd(call, bytesRead)
|
||||
}
|
||||
}
|
||||
return call.messageDone(
|
||||
@@ -273,7 +272,7 @@ class Exchange(
|
||||
try {
|
||||
if (invokeStartEvent) {
|
||||
invokeStartEvent = false
|
||||
eventListener.requestBodyStart(call)
|
||||
call.eventListener.requestBodyStart(call)
|
||||
}
|
||||
super.write(source, byteCount)
|
||||
this.bytesReceived += byteCount
|
||||
@@ -347,7 +346,7 @@ class Exchange(
|
||||
|
||||
if (invokeStartEvent) {
|
||||
invokeStartEvent = false
|
||||
eventListener.responseBodyStart(call)
|
||||
call.eventListener.responseBodyStart(call)
|
||||
}
|
||||
|
||||
if (read == -1L) {
|
||||
@@ -390,7 +389,7 @@ class Exchange(
|
||||
// If the body is closed without reading any bytes send a responseBodyStart() now.
|
||||
if (e == null && invokeStartEvent) {
|
||||
invokeStartEvent = false
|
||||
eventListener.responseBodyStart(call)
|
||||
call.eventListener.responseBodyStart(call)
|
||||
}
|
||||
return bodyComplete(
|
||||
bytesRead = bytesReceived,
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater
|
||||
import kotlin.reflect.KClass
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
@@ -70,7 +71,8 @@ class RealCall(
|
||||
Lockable {
|
||||
private val connectionPool: RealConnectionPool = client.connectionPool.delegate
|
||||
|
||||
internal val eventListener: EventListener = client.eventListenerFactory.create(this)
|
||||
@Volatile
|
||||
internal var eventListener: EventListener = client.eventListenerFactory.create(this)
|
||||
|
||||
private val timeout =
|
||||
object : AsyncTimeout() {
|
||||
@@ -126,6 +128,13 @@ class RealCall(
|
||||
|
||||
override fun timeout(): Timeout = timeout
|
||||
|
||||
override fun addEventListener(eventListener: EventListener) {
|
||||
// Atomically replace the current eventListener with a composite one.
|
||||
do {
|
||||
val previous = this.eventListener
|
||||
} while (!eventListenerUpdater.compareAndSet(this, previous, previous + eventListener))
|
||||
}
|
||||
|
||||
override fun <T : Any> tag(type: KClass<T>): T? = type.java.cast(tags.get()[type])
|
||||
|
||||
override fun <T> tag(type: Class<out T>): T? = tag(type.kotlin)
|
||||
@@ -202,7 +211,7 @@ class RealCall(
|
||||
interceptors += client.interceptors
|
||||
interceptors += RetryAndFollowUpInterceptor(client)
|
||||
interceptors += BridgeInterceptor(client.cookieJar)
|
||||
interceptors += CacheInterceptor(client.cache)
|
||||
interceptors += CacheInterceptor(this, client.cache)
|
||||
interceptors += ConnectInterceptor
|
||||
if (!forWebSocket) {
|
||||
interceptors += client.networkInterceptors
|
||||
@@ -297,7 +306,7 @@ class RealCall(
|
||||
val exchangeFinder = this.exchangeFinder!!
|
||||
val connection = exchangeFinder.find()
|
||||
val codec = connection.newCodec(client, chain)
|
||||
val result = Exchange(this, eventListener, exchangeFinder, codec)
|
||||
val result = Exchange(this, exchangeFinder, codec)
|
||||
this.interceptorScopedExchange = result
|
||||
this.exchange = result
|
||||
withLock {
|
||||
@@ -608,4 +617,13 @@ class RealCall(
|
||||
*/
|
||||
val callStackTrace: Any?,
|
||||
) : WeakReference<RealCall>(referent)
|
||||
|
||||
private companion object {
|
||||
val eventListenerUpdater: AtomicReferenceFieldUpdater<RealCall, EventListener> =
|
||||
AtomicReferenceFieldUpdater.newUpdater(
|
||||
RealCall::class.java,
|
||||
EventListener::class.java,
|
||||
"eventListener",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package okhttp3
|
||||
|
||||
import app.cash.burst.Burst
|
||||
import assertk.all
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.contains
|
||||
@@ -25,6 +26,7 @@ import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isFalse
|
||||
import assertk.assertions.isIn
|
||||
import assertk.assertions.isInstanceOf
|
||||
import assertk.assertions.isNotEmpty
|
||||
import assertk.assertions.isNotNull
|
||||
import assertk.assertions.isNull
|
||||
import assertk.assertions.isSameAs
|
||||
@@ -95,6 +97,7 @@ import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.MatcherAssert
|
||||
import org.junit.Assume.assumeThat
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Tag
|
||||
@@ -105,7 +108,10 @@ import org.junit.jupiter.api.extension.RegisterExtension
|
||||
@Flaky // STDOUT logging enabled for test
|
||||
@Timeout(30)
|
||||
@Tag("Slow")
|
||||
class EventListenerTest {
|
||||
@Burst
|
||||
class EventListenerTest(
|
||||
val listenerInstalledOn: ListenerInstalledOn = ListenerInstalledOn.Client,
|
||||
) {
|
||||
@RegisterExtension
|
||||
val platform = PlatformRule()
|
||||
|
||||
@@ -120,8 +126,11 @@ class EventListenerTest {
|
||||
private var client =
|
||||
clientTestRule
|
||||
.newClientBuilder()
|
||||
.eventListenerFactory(clientTestRule.wrap(eventRecorder))
|
||||
.build()
|
||||
.apply {
|
||||
if (listenerInstalledOn == ListenerInstalledOn.Client) {
|
||||
eventListenerFactory(clientTestRule.wrap(eventRecorder))
|
||||
}
|
||||
}.build()
|
||||
private var socksProxy: SocksProxy? = null
|
||||
private var cache: Cache? = null
|
||||
|
||||
@@ -151,7 +160,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -192,7 +201,7 @@ class EventListenerTest {
|
||||
)
|
||||
val ipAddress = InetAddress.getLoopbackAddress().hostAddress
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(
|
||||
@@ -235,7 +244,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -296,7 +305,7 @@ class EventListenerTest {
|
||||
.readTimeout(Duration.ofMillis(250))
|
||||
.build()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -345,7 +354,7 @@ class EventListenerTest {
|
||||
.readTimeout(Duration.ofMillis(250))
|
||||
.build()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -383,7 +392,7 @@ class EventListenerTest {
|
||||
@Test
|
||||
fun canceledCallEventSequence() {
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -411,7 +420,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -446,7 +455,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -462,7 +471,7 @@ class EventListenerTest {
|
||||
emptyBody: Boolean = false,
|
||||
) {
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -520,7 +529,7 @@ class EventListenerTest {
|
||||
server.enqueue(MockResponse())
|
||||
server.enqueue(MockResponse())
|
||||
client
|
||||
.newCall(
|
||||
.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -529,7 +538,7 @@ class EventListenerTest {
|
||||
.close()
|
||||
eventRecorder.removeUpToEvent<CallEnd>()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -703,7 +712,7 @@ class EventListenerTest {
|
||||
fun successfulDnsLookup() {
|
||||
server.enqueue(MockResponse())
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -728,7 +737,7 @@ class EventListenerTest {
|
||||
|
||||
// Seed the pool.
|
||||
val call1 =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -739,7 +748,7 @@ class EventListenerTest {
|
||||
response1.body.close()
|
||||
eventRecorder.clearAllEvents()
|
||||
val call2 =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -772,7 +781,7 @@ class EventListenerTest {
|
||||
.dns(dns)
|
||||
.build()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url("http://fakeurl:" + server.port)
|
||||
@@ -795,7 +804,7 @@ class EventListenerTest {
|
||||
.dns(FakeDns())
|
||||
.build()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url("http://fakeurl/")
|
||||
@@ -819,7 +828,7 @@ class EventListenerTest {
|
||||
.dns(emptyDns)
|
||||
.build()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url("http://fakeurl/")
|
||||
@@ -840,7 +849,7 @@ class EventListenerTest {
|
||||
fun successfulConnect() {
|
||||
server.enqueue(MockResponse())
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -855,7 +864,7 @@ class EventListenerTest {
|
||||
assertThat(connectStart.call).isSameAs(call)
|
||||
assertThat(connectStart.inetSocketAddress).isEqualTo(expectedAddress)
|
||||
assertThat(connectStart.proxy).isEqualTo(Proxy.NO_PROXY)
|
||||
val connectEnd = eventRecorder.removeUpToEvent<ConnectEnd>()
|
||||
val connectEnd = eventRecorder.removeUpToEvent<CallEvent.ConnectEnd>()
|
||||
assertThat(connectEnd.call).isSameAs(call)
|
||||
assertThat(connectEnd.inetSocketAddress).isEqualTo(expectedAddress)
|
||||
assertThat(connectEnd.protocol).isEqualTo(Protocol.HTTP_1_1)
|
||||
@@ -871,7 +880,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -909,7 +918,7 @@ class EventListenerTest {
|
||||
.dns(DoubleInetAddressDns())
|
||||
.build()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -933,7 +942,7 @@ class EventListenerTest {
|
||||
.proxy(server.proxyAddress)
|
||||
.build()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url("http://www.fakeurl")
|
||||
@@ -969,7 +978,7 @@ class EventListenerTest {
|
||||
.proxy(proxy)
|
||||
.build()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url("http://" + SocksProxy.HOSTNAME_THAT_ONLY_THE_PROXY_KNOWS + ":" + server.port)
|
||||
@@ -1019,7 +1028,7 @@ class EventListenerTest {
|
||||
.proxyAuthenticator(RecordingOkAuthenticator("password", "Basic"))
|
||||
.build()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1040,7 +1049,7 @@ class EventListenerTest {
|
||||
enableTlsWithTunnel()
|
||||
server.enqueue(MockResponse())
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1066,7 +1075,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1098,7 +1107,7 @@ class EventListenerTest {
|
||||
.proxy(server.proxyAddress)
|
||||
.build()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1130,7 +1139,7 @@ class EventListenerTest {
|
||||
.dns(DoubleInetAddressDns())
|
||||
.build()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1158,7 +1167,7 @@ class EventListenerTest {
|
||||
|
||||
// Seed the pool.
|
||||
val call1 =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1169,7 +1178,7 @@ class EventListenerTest {
|
||||
response1.body.close()
|
||||
eventRecorder.clearAllEvents()
|
||||
val call2 =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1187,7 +1196,7 @@ class EventListenerTest {
|
||||
fun successfulConnectionFound() {
|
||||
server.enqueue(MockResponse())
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1217,7 +1226,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1237,7 +1246,7 @@ class EventListenerTest {
|
||||
|
||||
// Seed the pool.
|
||||
val call1 =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1249,7 +1258,7 @@ class EventListenerTest {
|
||||
val connectionAcquired1 = eventRecorder.removeUpToEvent<ConnectionAcquired>()
|
||||
eventRecorder.clearAllEvents()
|
||||
val call2 =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1281,7 +1290,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1324,7 +1333,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1354,7 +1363,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1393,7 +1402,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1433,7 +1442,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1491,7 +1500,7 @@ class EventListenerTest {
|
||||
)
|
||||
val request = NonCompletingRequestBody()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1564,7 +1573,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1665,7 +1674,7 @@ class EventListenerTest {
|
||||
.setLevel(HttpLoggingInterceptor.Level.BODY),
|
||||
).build()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1709,7 +1718,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1761,7 +1770,7 @@ class EventListenerTest {
|
||||
// Warm up the client so the timing part of the test gets a pooled connection.
|
||||
server.enqueue(MockResponse())
|
||||
val warmUpCall =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1796,7 +1805,7 @@ class EventListenerTest {
|
||||
|
||||
// Create a request body with artificial delays.
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -1868,7 +1877,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
server.enqueue(MockResponse())
|
||||
val call = client.newCall(Request.Builder().url(server.url("/")).build())
|
||||
val call = client.newCallWithListener(Request.Builder().url(server.url("/")).build())
|
||||
call.execute()
|
||||
assertThat(eventRecorder.recordedEventTypes()).containsExactly(
|
||||
CallStart::class,
|
||||
@@ -1913,7 +1922,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
otherServer.enqueue(MockResponse())
|
||||
val call = client.newCall(Request.Builder().url(server.url("/")).build())
|
||||
val call = client.newCallWithListener(Request.Builder().url(server.url("/")).build())
|
||||
call.execute()
|
||||
assertThat(eventRecorder.recordedEventTypes()).containsExactly(
|
||||
CallStart::class,
|
||||
@@ -1970,7 +1979,7 @@ class EventListenerTest {
|
||||
chain.proceed(chain.request())
|
||||
},
|
||||
).build()
|
||||
val call = client.newCall(Request.Builder().url(server.url("/")).build())
|
||||
val call = client.newCallWithListener(Request.Builder().url(server.url("/")).build())
|
||||
val response = call.execute()
|
||||
assertThat(response.body.string()).isEqualTo("b")
|
||||
assertThat(eventRecorder.recordedEventTypes()).containsExactly(
|
||||
@@ -2020,7 +2029,7 @@ class EventListenerTest {
|
||||
.build()
|
||||
},
|
||||
).build()
|
||||
val call = client.newCall(Request.Builder().url(server.url("/")).build())
|
||||
val call = client.newCallWithListener(Request.Builder().url(server.url("/")).build())
|
||||
val response = call.execute()
|
||||
assertThat(response.body.string()).isEqualTo("a")
|
||||
assertThat(eventRecorder.recordedEventTypes())
|
||||
@@ -2043,7 +2052,7 @@ class EventListenerTest {
|
||||
.header("Expect", "100-continue")
|
||||
.post("abc".toRequestBody("text/plain".toMediaType()))
|
||||
.build()
|
||||
val call = client.newCall(request)
|
||||
val call = client.newCallWithListener(request)
|
||||
call.execute()
|
||||
assertThat(eventRecorder.recordedEventTypes()).containsExactly(
|
||||
CallStart::class,
|
||||
@@ -2085,7 +2094,7 @@ class EventListenerTest {
|
||||
.header("Expect", "100-continue")
|
||||
.post("abc".toRequestBody("text/plain".toMediaType()))
|
||||
.build()
|
||||
val call = client.newCall(request)
|
||||
val call = client.newCallWithListener(request)
|
||||
call
|
||||
.execute()
|
||||
.use { response -> assertThat(response.body.string()).isEqualTo("") }
|
||||
@@ -2105,7 +2114,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -2154,7 +2163,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
var call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -2164,7 +2173,7 @@ class EventListenerTest {
|
||||
assertThat(response.code).isEqualTo(200)
|
||||
response.close()
|
||||
eventRecorder.clearAllEvents()
|
||||
call = call.clone()
|
||||
call = call.cloneWithListener()
|
||||
response = call.execute()
|
||||
assertThat(response.code).isEqualTo(200)
|
||||
assertThat(response.body.string()).isEqualTo("abc")
|
||||
@@ -2205,7 +2214,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
var call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -2215,7 +2224,7 @@ class EventListenerTest {
|
||||
assertThat(response.code).isEqualTo(200)
|
||||
response.close()
|
||||
eventRecorder.clearAllEvents()
|
||||
call = call.clone()
|
||||
call = call.cloneWithListener()
|
||||
response = call.execute()
|
||||
assertThat(response.code).isEqualTo(200)
|
||||
assertThat(response.body.string()).isEqualTo("abd")
|
||||
@@ -2241,7 +2250,7 @@ class EventListenerTest {
|
||||
fun satisfactionFailure() {
|
||||
enableCache()
|
||||
val call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -2274,7 +2283,7 @@ class EventListenerTest {
|
||||
.build(),
|
||||
)
|
||||
var call =
|
||||
client.newCall(
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
@@ -2285,7 +2294,7 @@ class EventListenerTest {
|
||||
assertThat(response.body.string()).isEqualTo("abc")
|
||||
response.close()
|
||||
eventRecorder.clearAllEvents()
|
||||
call = call.clone()
|
||||
call = call.cloneWithListener()
|
||||
response = call.execute()
|
||||
assertThat(response.code).isEqualTo(200)
|
||||
assertThat(response.body.string()).isEqualTo("abc")
|
||||
@@ -2374,6 +2383,60 @@ class EventListenerTest {
|
||||
}
|
||||
}
|
||||
|
||||
/** Listeners added with [Call.addEventListener] don't exist on clones of that call. */
|
||||
@Test
|
||||
fun clonedCallDoesNotHaveAddedEventListeners() {
|
||||
assumeTrue(listenerInstalledOn != ListenerInstalledOn.Client)
|
||||
|
||||
server.enqueue(
|
||||
MockResponse
|
||||
.Builder()
|
||||
.body("abc")
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
.build(),
|
||||
)
|
||||
val clone = call.clone() // Not cloneWithListener.
|
||||
|
||||
val response = clone.execute()
|
||||
assertThat(response.code).isEqualTo(200)
|
||||
assertThat(response.body.string()).isEqualTo("abc")
|
||||
response.body.close()
|
||||
assertThat(eventRecorder.recordedEventTypes()).isEmpty()
|
||||
}
|
||||
|
||||
/** Listeners added with [OkHttpClient.Builder.eventListener] are also added to clones. */
|
||||
@Test
|
||||
fun clonedCallHasClientEventListeners() {
|
||||
assumeTrue(listenerInstalledOn == ListenerInstalledOn.Client)
|
||||
|
||||
server.enqueue(
|
||||
MockResponse
|
||||
.Builder()
|
||||
.body("abc")
|
||||
.build(),
|
||||
)
|
||||
val call =
|
||||
client.newCallWithListener(
|
||||
Request
|
||||
.Builder()
|
||||
.url(server.url("/"))
|
||||
.build(),
|
||||
)
|
||||
val clone = call.clone() // Not cloneWithListener().
|
||||
|
||||
val response = clone.execute()
|
||||
assertThat(response.code).isEqualTo(200)
|
||||
assertThat(response.body.string()).isEqualTo("abc")
|
||||
response.body.close()
|
||||
assertThat(eventRecorder.recordedEventTypes()).isNotEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map with sample values for each possible parameter of an [EventListener] function
|
||||
* parameter.
|
||||
@@ -2433,6 +2496,38 @@ class EventListenerTest {
|
||||
return Cache(cacheDir, (1024 * 1024).toLong())
|
||||
}
|
||||
|
||||
private fun OkHttpClient.newCallWithListener(request: Request): Call =
|
||||
newCall(request)
|
||||
.apply {
|
||||
addEventRecorder(eventRecorder)
|
||||
}
|
||||
|
||||
private fun Call.cloneWithListener(): Call =
|
||||
clone()
|
||||
.apply {
|
||||
addEventRecorder(eventRecorder)
|
||||
}
|
||||
|
||||
private fun Call.addEventRecorder(eventRecorder: EventRecorder) {
|
||||
when (listenerInstalledOn) {
|
||||
ListenerInstalledOn.Call -> {
|
||||
addEventListener(eventRecorder.eventListener)
|
||||
}
|
||||
|
||||
ListenerInstalledOn.Relay -> {
|
||||
addEventListener(EventListenerRelay(this, eventRecorder).eventListener)
|
||||
}
|
||||
|
||||
ListenerInstalledOn.Client -> {} // listener is added elsewhere.
|
||||
}
|
||||
}
|
||||
|
||||
enum class ListenerInstalledOn {
|
||||
Client,
|
||||
Call,
|
||||
Relay,
|
||||
}
|
||||
|
||||
companion object {
|
||||
val anyResponse = CoreMatchers.any(Response::class.java)
|
||||
}
|
||||
|
||||
@@ -202,6 +202,8 @@ class KotlinSourceModernTest {
|
||||
|
||||
override fun timeout(): Timeout = TODO()
|
||||
|
||||
override fun addEventListener(eventListener: EventListener) = TODO()
|
||||
|
||||
override fun <T : Any> tag(type: KClass<T>): T? = TODO()
|
||||
|
||||
override fun <T> tag(type: Class<out T>): T? = TODO()
|
||||
|
||||
Reference in New Issue
Block a user