-
Notifications
You must be signed in to change notification settings - Fork 9.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Read the response even if writing the request fails #6295
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ import java.net.ProtocolException | |
import okhttp3.Interceptor | ||
import okhttp3.Response | ||
import okhttp3.internal.EMPTY_RESPONSE | ||
import okhttp3.internal.http2.ConnectionShutdownException | ||
import okio.buffer | ||
|
||
/** This is the last interceptor in the chain. It makes a network call to the server. */ | ||
|
@@ -33,98 +34,118 @@ class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor { | |
val requestBody = request.body | ||
val sentRequestMillis = System.currentTimeMillis() | ||
|
||
exchange.writeRequestHeaders(request) | ||
|
||
var invokeStartEvent = true | ||
var responseBuilder: Response.Builder? = null | ||
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) { | ||
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100 | ||
// Continue" response before transmitting the request body. If we don't get that, return | ||
// what we did get (such as a 4xx response) without ever transmitting the request body. | ||
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) { | ||
exchange.flushRequest() | ||
responseBuilder = exchange.readResponseHeaders(expectContinue = true) | ||
exchange.responseHeadersStart() | ||
invokeStartEvent = false | ||
} | ||
if (responseBuilder == null) { | ||
if (requestBody.isDuplex()) { | ||
// Prepare a duplex body so that the application can send a request body later. | ||
var sendRequestException: IOException? = null | ||
try { | ||
exchange.writeRequestHeaders(request) | ||
|
||
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) { | ||
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100 | ||
// Continue" response before transmitting the request body. If we don't get that, return | ||
// what we did get (such as a 4xx response) without ever transmitting the request body. | ||
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) { | ||
exchange.flushRequest() | ||
val bufferedRequestBody = exchange.createRequestBody(request, true).buffer() | ||
requestBody.writeTo(bufferedRequestBody) | ||
responseBuilder = exchange.readResponseHeaders(expectContinue = true) | ||
exchange.responseHeadersStart() | ||
invokeStartEvent = false | ||
} | ||
if (responseBuilder == null) { | ||
if (requestBody.isDuplex()) { | ||
// Prepare a duplex body so that the application can send a request body later. | ||
exchange.flushRequest() | ||
val bufferedRequestBody = exchange.createRequestBody(request, true).buffer() | ||
requestBody.writeTo(bufferedRequestBody) | ||
} else { | ||
// Write the request body if the "Expect: 100-continue" expectation was met. | ||
val bufferedRequestBody = exchange.createRequestBody(request, false).buffer() | ||
requestBody.writeTo(bufferedRequestBody) | ||
bufferedRequestBody.close() | ||
} | ||
} else { | ||
// Write the request body if the "Expect: 100-continue" expectation was met. | ||
val bufferedRequestBody = exchange.createRequestBody(request, false).buffer() | ||
requestBody.writeTo(bufferedRequestBody) | ||
bufferedRequestBody.close() | ||
exchange.noRequestBody() | ||
if (!exchange.connection.isMultiplexed) { | ||
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection | ||
// from being reused. Otherwise we're still obligated to transmit the request body to | ||
// leave the connection in a consistent state. | ||
exchange.noNewExchangesOnConnection() | ||
} | ||
} | ||
} else { | ||
exchange.noRequestBody() | ||
if (!exchange.connection.isMultiplexed) { | ||
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection | ||
// from being reused. Otherwise we're still obligated to transmit the request body to | ||
// leave the connection in a consistent state. | ||
exchange.noNewExchangesOnConnection() | ||
} | ||
} | ||
} else { | ||
exchange.noRequestBody() | ||
} | ||
|
||
if (requestBody == null || !requestBody.isDuplex()) { | ||
exchange.finishRequest() | ||
} | ||
if (responseBuilder == null) { | ||
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!! | ||
if (invokeStartEvent) { | ||
exchange.responseHeadersStart() | ||
invokeStartEvent = false | ||
if (requestBody == null || !requestBody.isDuplex()) { | ||
exchange.finishRequest() | ||
} | ||
} catch (e: IOException) { | ||
if (e is ConnectionShutdownException) { | ||
throw e // No request was sent so there's no response to read. | ||
} | ||
if (!exchange.hasFailure) { | ||
throw e // Don't attempt to read the response; we failed to send the request. | ||
} | ||
sendRequestException = e | ||
} | ||
var response = responseBuilder | ||
.request(request) | ||
.handshake(exchange.connection.handshake()) | ||
.sentRequestAtMillis(sentRequestMillis) | ||
.receivedResponseAtMillis(System.currentTimeMillis()) | ||
.build() | ||
var code = response.code | ||
if (code == 100) { | ||
// Server sent a 100-continue even though we did not request one. Try again to read the actual | ||
// response status. | ||
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!! | ||
if (invokeStartEvent) { | ||
exchange.responseHeadersStart() | ||
|
||
try { | ||
if (responseBuilder == null) { | ||
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!! | ||
if (invokeStartEvent) { | ||
exchange.responseHeadersStart() | ||
invokeStartEvent = false | ||
} | ||
} | ||
response = responseBuilder | ||
var response = responseBuilder | ||
.request(request) | ||
.handshake(exchange.connection.handshake()) | ||
.sentRequestAtMillis(sentRequestMillis) | ||
.receivedResponseAtMillis(System.currentTimeMillis()) | ||
.build() | ||
code = response.code | ||
} | ||
var code = response.code | ||
if (code == 100) { | ||
// Server sent a 100-continue even though we did not request one. Try again to read the | ||
// actual response status. | ||
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!! | ||
if (invokeStartEvent) { | ||
exchange.responseHeadersStart() | ||
} | ||
response = responseBuilder | ||
.request(request) | ||
.handshake(exchange.connection.handshake()) | ||
.sentRequestAtMillis(sentRequestMillis) | ||
.receivedResponseAtMillis(System.currentTimeMillis()) | ||
.build() | ||
code = response.code | ||
} | ||
|
||
exchange.responseHeadersEnd(response) | ||
exchange.responseHeadersEnd(response) | ||
|
||
response = if (forWebSocket && code == 101) { | ||
// Connection is upgrading, but we need to ensure interceptors see a non-null response body. | ||
response.newBuilder() | ||
.body(EMPTY_RESPONSE) | ||
.build() | ||
} else { | ||
response.newBuilder() | ||
.body(exchange.openResponseBody(response)) | ||
.build() | ||
} | ||
if ("close".equals(response.request.header("Connection"), ignoreCase = true) || | ||
"close".equals(response.header("Connection"), ignoreCase = true)) { | ||
exchange.noNewExchangesOnConnection() | ||
} | ||
if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) { | ||
throw ProtocolException( | ||
"HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}") | ||
response = if (forWebSocket && code == 101) { | ||
// Connection is upgrading, but we need to ensure interceptors see a non-null response body. | ||
response.newBuilder() | ||
.body(EMPTY_RESPONSE) | ||
.build() | ||
} else { | ||
response.newBuilder() | ||
.body(exchange.openResponseBody(response)) | ||
.build() | ||
} | ||
if ("close".equals(response.request.header("Connection"), ignoreCase = true) || | ||
"close".equals(response.header("Connection"), ignoreCase = true)) { | ||
exchange.noNewExchangesOnConnection() | ||
} | ||
if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) { | ||
throw ProtocolException( | ||
"HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}") | ||
} | ||
return response | ||
} catch (e: IOException) { | ||
if (sendRequestException != null) { | ||
sendRequestException.addSuppressed(e) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! |
||
throw sendRequestException | ||
} | ||
throw e | ||
} | ||
return response | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1118,7 +1118,7 @@ private void writeChunk(BufferedSink sink) throws IOException { | |
assertThat(listener.recordedEventTypes()).containsExactly( | ||
"CallStart", "ProxySelectStart", "ProxySelectEnd", "DnsStart", "DnsEnd", "ConnectStart", | ||
"ConnectEnd", "ConnectionAcquired", "RequestHeadersStart", "RequestHeadersEnd", | ||
"RequestBodyStart", "RequestFailed", "ConnectionReleased", "CallFailed"); | ||
"RequestBodyStart", "RequestFailed", "ResponseFailed", "ConnectionReleased", "CallFailed"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we have a test for this where it does successfully read the response? That is, even though server didn't read the request? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, the new test case includes that. |
||
} | ||
|
||
@Test public void requestBodySuccessHttp1OverHttps() throws IOException { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the diffs on this file are worth viewing in 'ignore whitespace' mode because there’s a lot of indentation change