Skip to content

Commit e11c48c

Browse files
committed
Read response even if failed sending request headers (square#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 square#8712 (cherry picked from commit 8fde8e9)
1 parent 522fe81 commit e11c48c

File tree

2 files changed

+121
-1
lines changed

2 files changed

+121
-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: 118 additions & 0 deletions
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)