Skip to content

Commit 331eabd

Browse files
committed
Add a cacheUrlOverride
1 parent ee9ebba commit 331eabd

File tree

6 files changed

+110
-15
lines changed

6 files changed

+110
-15
lines changed

okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/DnsOverHttps.kt

+20-6
Original file line numberDiff line numberDiff line change
@@ -186,16 +186,24 @@ class DnsOverHttps internal constructor(
186186
throw unknownHostException
187187
}
188188

189-
private fun getCacheOnlyResponse(request: Request): Response? {
190-
if (!post && client.cache != null) {
189+
private fun getCacheOnlyResponse(
190+
request: Request,
191+
): Response? {
192+
if (client.cache != null) {
191193
try {
192194
// Use the cache without hitting the network first
193195
// 504 code indicates that the Cache is stale
194-
val preferCache =
196+
val onlyIfCached =
195197
CacheControl.Builder()
196198
.onlyIfCached()
197199
.build()
198-
val cacheRequest = request.newBuilder().cacheControl(preferCache).build()
200+
201+
var cacheUrl = request.url
202+
203+
val cacheRequest = request.newBuilder()
204+
.cacheControl(onlyIfCached)
205+
.cacheUrlOverride(cacheUrl)
206+
.build()
199207

200208
val cacheResponse = client.newCall(cacheRequest).execute()
201209

@@ -247,7 +255,12 @@ class DnsOverHttps internal constructor(
247255
val query = DnsRecordCodec.encodeQuery(hostname, type)
248256

249257
if (post) {
250-
url(url).post(query.toRequestBody(DNS_MESSAGE))
258+
url(url)
259+
.cacheUrlOverride(
260+
url.newBuilder()
261+
.addQueryParameter("hostname", hostname).build()
262+
)
263+
.post(query.toRequestBody(DNS_MESSAGE))
251264
} else {
252265
val encoded = query.base64Url().replace("=", "")
253266
val requestUrl = url.newBuilder().addQueryParameter("dns", encoded).build()
@@ -313,7 +326,8 @@ class DnsOverHttps internal constructor(
313326
this.bootstrapDnsHosts = bootstrapDnsHosts
314327
}
315328

316-
fun bootstrapDnsHosts(vararg bootstrapDnsHosts: InetAddress): Builder = bootstrapDnsHosts(bootstrapDnsHosts.toList())
329+
fun bootstrapDnsHosts(vararg bootstrapDnsHosts: InetAddress): Builder =
330+
bootstrapDnsHosts(bootstrapDnsHosts.toList())
317331

318332
fun systemDns(systemDns: Dns) =
319333
apply {

okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsOverHttpsTest.kt

+51
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ import java.util.concurrent.TimeUnit
3131
import mockwebserver3.MockResponse
3232
import mockwebserver3.MockWebServer
3333
import okhttp3.Cache
34+
import okhttp3.Call
3435
import okhttp3.Dns
36+
import okhttp3.EventListener
3537
import okhttp3.OkHttpClient
3638
import okhttp3.Protocol
39+
import okhttp3.Response
3740
import okhttp3.testing.PlatformRule
3841
import okio.Buffer
3942
import okio.ByteString.Companion.decodeHex
@@ -55,6 +58,27 @@ class DnsOverHttpsTest {
5558
private val bootstrapClient =
5659
OkHttpClient.Builder()
5760
.protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1))
61+
.eventListener(object : EventListener() {
62+
override fun callStart(call: Call) {
63+
println("callStart " + call.request().url + " " + call.request().cacheUrlOverride)
64+
}
65+
66+
override fun satisfactionFailure(call: Call, response: Response) {
67+
println("satisfactionFailure " + call.request().url)
68+
}
69+
70+
override fun cacheHit(call: Call, response: Response) {
71+
println("cacheHit " + call.request().url)
72+
}
73+
74+
override fun cacheMiss(call: Call) {
75+
println("cacheMiss " + call.request().url)
76+
}
77+
78+
override fun cacheConditionalHit(call: Call, cachedResponse: Response) {
79+
println("cacheConditionalHit " + call.request().url)
80+
}
81+
})
5882
.build()
5983

6084
@BeforeEach
@@ -193,6 +217,31 @@ class DnsOverHttpsTest {
193217
assertThat(result).isEqualTo(listOf(address("157.240.1.18")))
194218
}
195219

220+
@Test
221+
fun usesCacheEvenForPost() {
222+
val cache = Cache("cache".toPath(), (100 * 1024).toLong(), cacheFs)
223+
val cachedClient = bootstrapClient.newBuilder().cache(cache).build()
224+
val cachedDns = buildLocalhost(cachedClient, false, post = true)
225+
server.enqueue(
226+
dnsResponse(
227+
"0000818000010003000000000567726170680866616365626f6f6b03636f6d0000010001c00c000500010" +
228+
"0000a6d000603617069c012c0300005000100000cde000c04737461720463313072c012c04200010001000" +
229+
"0003b00049df00112",
230+
)
231+
.newBuilder()
232+
.setHeader("cache-control", "private, max-age=298")
233+
.build(),
234+
)
235+
var result = cachedDns.lookup("google.com")
236+
assertThat(result).containsExactly(address("157.240.1.18"))
237+
val recordedRequest = server.takeRequest()
238+
assertThat(recordedRequest.method).isEqualTo("POST")
239+
assertThat(recordedRequest.path)
240+
.isEqualTo("/lookup?ct")
241+
result = cachedDns.lookup("google.com")
242+
assertThat(result).isEqualTo(listOf(address("157.240.1.18")))
243+
}
244+
196245
@Test
197246
fun usesCacheOnlyIfFresh() {
198247
val cache = Cache(File("./target/DnsOverHttpsTest.cache"), 100 * 1024L)
@@ -245,12 +294,14 @@ class DnsOverHttpsTest {
245294
private fun buildLocalhost(
246295
bootstrapClient: OkHttpClient,
247296
includeIPv6: Boolean,
297+
post: Boolean = false
248298
): DnsOverHttps {
249299
val url = server.url("/lookup?ct")
250300
return DnsOverHttps.Builder().client(bootstrapClient)
251301
.includeIPv6(includeIPv6)
252302
.resolvePrivateAddresses(true)
253303
.url(url)
304+
.post(post)
254305
.build()
255306
}
256307

okhttp/src/main/kotlin/okhttp3/Request.kt

+15
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ class Request internal constructor(builder: Builder) {
5454
@get:JvmName("body")
5555
val body: RequestBody? = builder.body
5656

57+
@get:JvmName("cacheUrlOverride")
58+
val cacheUrlOverride: HttpUrl? = builder.cacheUrlOverride
59+
5760
internal val tags: Map<KClass<*>, Any> = builder.tags.toMap()
5861

5962
internal var lazyCacheControl: CacheControl? = null
@@ -183,6 +186,7 @@ class Request internal constructor(builder: Builder) {
183186
internal var method: String
184187
internal var headers: Headers.Builder
185188
internal var body: RequestBody? = null
189+
internal var cacheUrlOverride: HttpUrl? = null
186190

187191
/** A mutable map of tags, or an immutable empty map if we don't have any. */
188192
internal var tags = mapOf<KClass<*>, Any>()
@@ -202,6 +206,7 @@ class Request internal constructor(builder: Builder) {
202206
else -> request.tags.toMutableMap()
203207
}
204208
this.headers = request.headers.newBuilder()
209+
this.cacheUrlOverride = request.cacheUrlOverride
205210
}
206211

207212
open fun url(url: HttpUrl): Builder =
@@ -316,6 +321,16 @@ class Request internal constructor(builder: Builder) {
316321
tag: T?,
317322
) = commonTag(type.kotlin, tag)
318323

324+
/**
325+
* Override the [Request.url] for caching, if it is either polluted with
326+
* transient query params, or has a canonical URL possibly for a CDN.
327+
*
328+
* If set, this will also allow caching for POST requests.
329+
*/
330+
fun cacheUrlOverride(cacheUrlOverride: HttpUrl?) = apply {
331+
this.cacheUrlOverride = cacheUrlOverride
332+
}
333+
319334
open fun build(): Request = Request(this)
320335
}
321336
}

okhttp/src/main/kotlin/okhttp3/internal/cache/CacheInterceptor.kt

+17-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import okhttp3.EventListener
2525
import okhttp3.Headers
2626
import okhttp3.Interceptor
2727
import okhttp3.Protocol
28+
import okhttp3.Request
2829
import okhttp3.Response
2930
import okhttp3.internal.closeQuietly
3031
import okhttp3.internal.connection.RealCall
@@ -44,7 +45,7 @@ class CacheInterceptor(internal val cache: Cache?) : Interceptor {
4445
@Throws(IOException::class)
4546
override fun intercept(chain: Interceptor.Chain): Response {
4647
val call = chain.call()
47-
val cacheCandidate = cache?.get(chain.request())
48+
val cacheCandidate = cache?.get(chain.request().requestForCache())
4849

4950
val now = System.currentTimeMillis()
5051

@@ -125,14 +126,17 @@ class CacheInterceptor(internal val cache: Cache?) : Interceptor {
125126
}
126127
}
127128

129+
val cacheNetworkRequest = networkRequest.requestForCache()
130+
128131
val response =
129132
networkResponse!!.newBuilder()
130133
.cacheResponse(cacheResponse?.stripBody())
131134
.networkResponse(networkResponse.stripBody())
135+
.request(cacheNetworkRequest)
132136
.build()
133137

134138
if (cache != null) {
135-
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
139+
if (response.promisesBody() && CacheStrategy.isCacheable(response)) {
136140
// Offer this request to the cache.
137141
val cacheRequest = cache.put(response)
138142
return cacheWritingResponse(cacheRequest, response).also {
@@ -285,3 +289,14 @@ class CacheInterceptor(internal val cache: Cache?) : Interceptor {
285289
}
286290
}
287291
}
292+
293+
private fun Request.requestForCache(): Request {
294+
return if (cacheUrlOverride != null) {
295+
newBuilder()
296+
.get()
297+
.cacheUrlOverride(null)
298+
.build()
299+
} else {
300+
this
301+
}
302+
}

okhttp/src/main/kotlin/okhttp3/internal/cache/CacheStrategy.kt

+2-3
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ class CacheStrategy internal constructor(
152152
// If this response shouldn't have been stored, it should never be used as a response source.
153153
// This check should be redundant as long as the persistence store is well-behaved and the
154154
// rules are constant.
155-
if (!isCacheable(cacheResponse, request)) {
155+
if (!isCacheable(cacheResponse)) {
156156
return CacheStrategy(request, null)
157157
}
158158

@@ -292,7 +292,6 @@ class CacheStrategy internal constructor(
292292
/** Returns true if [response] can be stored to later serve another request. */
293293
fun isCacheable(
294294
response: Response,
295-
request: Request,
296295
): Boolean {
297296
// Always go to network for uncacheable response codes (RFC 7231 section 6.1), This
298297
// implementation doesn't support caching partial content.
@@ -334,7 +333,7 @@ class CacheStrategy internal constructor(
334333
}
335334

336335
// A 'no-store' directive on request or response prevents the response from being cached.
337-
return !response.cacheControl.noStore && !request.cacheControl.noStore
336+
return !response.cacheControl.noStore && !response.request.cacheControl.noStore
338337
}
339338
}
340339
}

okhttp/src/test/java/okhttp3/CacheTest.kt

+5-4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import mockwebserver3.RecordedRequest
4343
import mockwebserver3.SocketPolicy.DisconnectAtEnd
4444
import mockwebserver3.junit5.internal.MockWebServerInstance
4545
import okhttp3.Cache.Companion.key
46+
import okhttp3.Cache.Companion.keyUrl
4647
import okhttp3.Headers.Companion.headersOf
4748
import okhttp3.MediaType.Companion.toMediaType
4849
import okhttp3.RequestBody.Companion.toRequestBody
@@ -2707,7 +2708,7 @@ class CacheTest {
27072708
.build(),
27082709
)
27092710
val url = server.url("/")
2710-
val urlKey = key(url)
2711+
val urlKey = keyUrl(url)
27112712
val entryMetadata =
27122713
"""
27132714
$url
@@ -2756,7 +2757,7 @@ CLEAN $urlKey ${entryMetadata.length} ${entryBody.length}
27562757
@Test
27572758
fun testGoldenCacheHttpsResponseOkHttp27() {
27582759
val url = server.url("/")
2759-
val urlKey = key(url)
2760+
val urlKey = keyUrl(url)
27602761
val prefix = get().getPrefix()
27612762
val entryMetadata =
27622763
"""
@@ -2804,7 +2805,7 @@ CLEAN $urlKey ${entryMetadata.length} ${entryBody.length}
28042805
@Test
28052806
fun testGoldenCacheHttpsResponseOkHttp30() {
28062807
val url = server.url("/")
2807-
val urlKey = key(url)
2808+
val urlKey = keyUrl(url)
28082809
val prefix = get().getPrefix()
28092810
val entryMetadata =
28102811
"""
@@ -2856,7 +2857,7 @@ CLEAN $urlKey ${entryMetadata.length} ${entryBody.length}
28562857
@Test
28572858
fun testGoldenCacheHttpResponseOkHttp30() {
28582859
val url = server.url("/")
2859-
val urlKey = key(url)
2860+
val urlKey = keyUrl(url)
28602861
val prefix = get().getPrefix()
28612862
val entryMetadata =
28622863
"""

0 commit comments

Comments
 (0)