Skip to content

Commit 949262e

Browse files
authored
[4.x] Backport 1XX handling from master (#7634)
* Ignore early hints for 4.9.x (#7444) (cherry picked from commit b565eec) * Expand 103 handling to other non-specific 1XX messages. (#7629) (cherry picked from commit 7e4576c) * Cleanup * Add a test of 103 handling * Add copyright * Add copyright * Fix cache test for 102/103 (#7633) (cherry picked from commit 930f138) * Disable test
1 parent 81d3411 commit 949262e

File tree

5 files changed

+89
-6
lines changed

5 files changed

+89
-6
lines changed

okhttp-testing-support/src/main/kotlin/okhttp3/OkHttpClientTestRule.kt

+6
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,12 @@ class OkHttpClientTestRule : TestRule {
264264
}
265265
}
266266

267+
fun eventsList(): List<String> {
268+
return synchronized(clientEventsList) {
269+
clientEventsList.toList()
270+
}
271+
}
272+
267273
companion object {
268274
/**
269275
* A network that resolves only one IP address per host. Use this when testing route selection

okhttp/src/main/kotlin/okhttp3/internal/http/CallServerInterceptor.kt

+15-3
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,8 @@ class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
103103
.receivedResponseAtMillis(System.currentTimeMillis())
104104
.build()
105105
var code = response.code
106-
if (code == 100) {
107-
// Server sent a 100-continue even though we did not request one. Try again to read the
108-
// actual response status.
106+
107+
if (shouldIgnoreAndWaitForRealResponse(code)) {
109108
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
110109
if (invokeStartEvent) {
111110
exchange.responseHeadersStart()
@@ -148,4 +147,17 @@ class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
148147
throw e
149148
}
150149
}
150+
151+
private fun shouldIgnoreAndWaitForRealResponse(code: Int): Boolean = when {
152+
// Server sent a 100-continue even though we did not request one. Try again to read the
153+
// actual response status.
154+
code == 100 -> true
155+
156+
// Handle Processing (102) & Early Hints (103) and any new codes without failing
157+
// 100 and 101 are the exceptions with different meanings
158+
// But Early Hints not currently exposed
159+
code in (102 until 200) -> true
160+
161+
else -> false
162+
}
151163
}

okhttp/src/main/kotlin/okhttp3/internal/http1/Http1ExchangeCodec.kt

+6
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@ class Http1ExchangeCodec(
193193
state = STATE_READ_RESPONSE_HEADERS
194194
responseBuilder
195195
}
196+
statusLine.code in (102 until 200) -> {
197+
// Processing and Early Hints will mean a second headers are coming.
198+
// Treat others the same for now
199+
state = STATE_READ_RESPONSE_HEADERS
200+
responseBuilder
201+
}
196202
else -> {
197203
state = STATE_OPEN_RESPONSE_BODY
198204
responseBuilder

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,10 @@ public final class CacheTest {
104104
// We can't test 100 because it's not really a response.
105105
// assertCached(false, 100);
106106
assertCached(false, 101);
107-
assertCached(false, 102);
107+
// We don't test 102 or 103 because it will break on the informational response with
108+
// mockwebserver.
109+
// assertSubsequentResponseCached( 102, 200);
110+
// assertSubsequentResponseCached( 103, 200);
108111
assertCached(true, 200);
109112
assertCached(false, 201);
110113
assertCached(false, 202);
@@ -151,7 +154,7 @@ public final class CacheTest {
151154
assertCached(false, 506);
152155
}
153156

154-
private void assertCached(boolean shouldPut, int responseCode) throws Exception {
157+
private void assertCached(boolean shouldWriteToCache, int responseCode) throws Exception {
155158
int expectedResponseCode = responseCode;
156159

157160
server = new MockWebServer();
@@ -193,7 +196,7 @@ private void assertCached(boolean shouldPut, int responseCode) throws Exception
193196
response.body().string();
194197

195198
Response cached = cacheGet(cache, request);
196-
if (shouldPut) {
199+
if (shouldWriteToCache) {
197200
assertThat(cached).isNotNull();
198201
cached.body().close();
199202
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (C) 2023 Block, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package okhttp3
17+
18+
import okhttp3.testing.PlatformRule
19+
import org.assertj.core.api.Assertions.assertThat
20+
import org.junit.Ignore
21+
import org.junit.Rule
22+
import org.junit.Test
23+
24+
@Ignore("For manual testing only")
25+
class InformationalResponseCodeTest {
26+
@JvmField @Rule
27+
val platform = PlatformRule()
28+
29+
@JvmField @Rule val clientTestRule = OkHttpClientTestRule().apply {
30+
recordFrames = true
31+
}
32+
33+
private var client = clientTestRule.newClient()
34+
35+
@Test
36+
fun test103() {
37+
// Pretend we are curl so cloudflare will send a 103
38+
val request = Request.Builder()
39+
.url("https://tradingstrategy.ai")
40+
.header("user-agent", "curl/7.85.0")
41+
.build()
42+
43+
val response = client.newCall(request).execute()
44+
45+
assertThat(response.code).isEqualTo(200)
46+
assertThat(response.protocol).isEqualTo(Protocol.HTTP_2)
47+
response.close()
48+
49+
val outgoingHeaders = ">> 0x00000003\\s+\\d+\\s+HEADERS\\s+END_STREAM\\|END_HEADERS".toRegex()
50+
assertThat(clientTestRule.eventsList().filter { it.matches(outgoingHeaders) }).hasSize(1)
51+
52+
// Confirm we get the informational response and final response headers.
53+
val incomingHeaders = "<< 0x00000003\\s+\\d+\\s+HEADERS\\s+END_HEADERS".toRegex()
54+
assertThat(clientTestRule.eventsList().filter { it.matches(incomingHeaders) }).hasSize(2)
55+
}
56+
}

0 commit comments

Comments
 (0)