Skip to content

Commit 8fde8e9

Browse files
authored
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
1 parent f021aca commit 8fde8e9

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ class Http1ExchangeCodec(
174174

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

0 commit comments

Comments
 (0)