Skip to content

Commit a2fe764

Browse files
authored
[4.x] Read response even if failed sending request headers (#8760)
* Read response even if failed sending request headers (#8759) * Handle failure while sending the request headers. We should still read the response headers if so, since the error could be > 431 "Request Header Fields Too Large" * Add SocketFailureTest to validate behavior on socket issues Introduce a new test class to simulate and handle socket failures during requests. This includes a scenario with large request headers and custom event listener to forcibly close sockets. The test ensures proper behavior under failure conditions. This reproduces #8712 (cherry picked from commit 8fde8e9) * Fixes for 4.x branch
1 parent 522fe81 commit a2fe764

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ class Http1ExchangeCodec(
170170
}
171171

172172
override fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
173-
check(state == STATE_OPEN_REQUEST_BODY ||
173+
check(
174+
state == STATE_IDLE ||
175+
state == STATE_OPEN_REQUEST_BODY ||
174176
state == STATE_WRITING_REQUEST_BODY ||
175177
state == STATE_READ_RESPONSE_HEADERS) {
176178
"state: $state"
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright (C) 2025 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.internal.http
17+
18+
import junit.framework.TestCase.fail
19+
import okhttp3.Call
20+
import okhttp3.Connection
21+
import okhttp3.EventListener
22+
import okhttp3.Headers
23+
import okhttp3.OkHttpClientTestRule
24+
import okhttp3.Request
25+
import okhttp3.mockwebserver.MockResponse
26+
import okhttp3.mockwebserver.MockWebServer
27+
import okhttp3.testing.PlatformRule
28+
import okio.IOException
29+
import org.junit.Before
30+
import org.junit.Rule
31+
import org.junit.Test
32+
import java.net.Socket
33+
import org.assertj.core.api.Assertions.assertThat
34+
35+
class SocketFailureTest {
36+
@get:Rule
37+
val platform = PlatformRule()
38+
39+
@get:Rule
40+
val clientTestRule = OkHttpClientTestRule()
41+
42+
val listener = SocketClosingEventListener()
43+
44+
private lateinit var server: MockWebServer
45+
private var client =
46+
clientTestRule
47+
.newClientBuilder()
48+
.eventListener(listener)
49+
.build()
50+
51+
class SocketClosingEventListener : EventListener() {
52+
var shouldClose: Boolean = false
53+
var lastSocket: Socket? = null
54+
55+
override fun connectionAcquired(
56+
call: Call,
57+
connection: Connection,
58+
) {
59+
lastSocket = connection.socket()
60+
}
61+
62+
override fun requestHeadersStart(call: Call) {
63+
if (shouldClose) {
64+
lastSocket!!.close()
65+
}
66+
}
67+
}
68+
69+
@Before
70+
fun setUp() {
71+
server = MockWebServer()
72+
}
73+
74+
@Test
75+
fun socketFailureOnLargeRequestHeaders() {
76+
server.enqueue(MockResponse())
77+
server.enqueue(MockResponse())
78+
server.start()
79+
80+
val call1 =
81+
client.newCall(
82+
Request
83+
.Builder()
84+
.url(server.url("/"))
85+
.build(),
86+
)
87+
call1.execute().use { response -> response.body?.string() }
88+
89+
listener.shouldClose = true
90+
// Large headers are a likely reason the servers would cut off the connection before it completes sending
91+
// request headers.
92+
// 431 "Request Header Fields Too Large"
93+
val largeHeaders =
94+
Headers
95+
.Builder()
96+
.apply {
97+
repeat(32) {
98+
add("name-$it", "value-$it-" + "0".repeat(1024))
99+
}
100+
}.build()
101+
val call2 =
102+
client.newCall(
103+
Request
104+
.Builder()
105+
.url(server.url("/"))
106+
.headers(largeHeaders)
107+
.build(),
108+
)
109+
110+
try {
111+
call2.execute()
112+
fail()
113+
} catch (ioe: IOException) {
114+
assertThat(ioe.message).isEqualTo("Socket closed")
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)