THRIFT-6060: Reconnect THttpClient after Connection: close - #3751
Open
1fanwang wants to merge 4 commits into
Open
THRIFT-6060: Reconnect THttpClient after Connection: close#37511fanwang wants to merge 4 commits into
1fanwang wants to merge 4 commits into
Conversation
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a C++ HTTP transport edge case where THttpClient could attempt to reuse a socket after an HTTP/1.1 server responded with Connection: close, causing subsequent RPCs to write to a connection the server had already closed.
Changes:
- Track
Connection: closeacross repeated and comma-separatedConnectionheaders and trigger a reconnect before sending the next non-empty request. - Reset buffered HTTP parsing/transfer state on
THttpClient::close()to avoid carrying stale HTTP state across reconnects. - Add a regression test that simulates a server sending
Connection: keep-alive, close(and repeatedConnectionheaders) and validates the client reconnects cleanly.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| lib/cpp/test/OneWayHTTPTest.cpp | Adds a regression test server transport that advertises Connection: close and verifies the client reconnects across multiple RPCs. |
| lib/cpp/src/thrift/transport/THttpClient.h | Adds closeAfterResponse_ state to track server-directed connection closure. |
| lib/cpp/src/thrift/transport/THttpClient.cpp | Implements parsing of Connection headers and reconnect-on-next-request behavior, plus HTTP state reset on close. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Comment on lines
82
to
94
| } else if (boost::istarts_with(header, "Content-Length")) { | ||
| chunked_ = false; | ||
| contentLength_ = atoi(value); | ||
| } else if (boost::istarts_with(header, "Connection")) { | ||
| std::vector<string> options; | ||
| boost::split(options, value, boost::is_any_of(",")); | ||
| for (const string& option : options) { | ||
| if (boost::iequals(boost::trim_copy(option), "close")) { | ||
| closeAfterResponse_ = true; | ||
| break; | ||
| } | ||
| } | ||
| } |
Comment on lines
56
to
69
| void THttpClient::close() { | ||
| onewayResponsePending_ = false; | ||
| closeAfterResponse_ = false; | ||
| readBuffer_.resetBuffer(); | ||
| readHeaders_ = true; | ||
| chunked_ = false; | ||
| chunkedDone_ = false; | ||
| chunkSize_ = 0; | ||
| contentLength_ = 0; | ||
| httpPos_ = 0; | ||
| httpBufLen_ = 0; | ||
| httpBuf_[0] = '\0'; | ||
| THttpTransport::close(); | ||
| } |
Comment on lines
+130
to
+131
| uint64_t acceptedCount() { | ||
| Synchronized sync(*this); |
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Comment on lines
+69
to
+74
| bool closesAfterHeader(const string& header) { | ||
| std::vector<char> buffer(header.begin(), header.end()); | ||
| buffer.push_back('\0'); | ||
| parseHeader(buffer.data()); | ||
| return closeAfterResponse_; | ||
| } |
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
lib/cpp/src/thrift/transport/THttpClient.cpp:82
parseHeader()still uses prefix matching forTransfer-EncodingandContent-Length(viaboost::istarts_with). This can misinterpret custom headers likeContent-Length-Mismatch:/Transfer-Encoding-Other:as the real hop-by-hop headers, similar to theConnection-Timeoutproblem this PR fixes forConnection. Consider parsing the header name up to the colon once (and trimming optional whitespace) and then doing exact case-insensitive comparisons for all supported header names.
if (boost::istarts_with(header, "Transfer-Encoding")) {
if (boost::iends_with(value, "chunked")) {
chunked_ = true;
}
} else if (boost::istarts_with(header, "Content-Length")) {
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A C++
THttpClientcan complete one RPC, then fail the next when an HTTP/1.1 server sendsConnection: close. The client leaves the socket marked open and writes the next request to a connection the server already closed.The client now records
closefrom comma-separated or repeatedConnectionheaders. It requires the exact field name, so headers such asConnection-Timeoutcannot trigger a reconnect. Before the next non-empty request, it clears buffered HTTP state, closes the old socket, and reconnects. Empty flushes remain no-ops, including when a buffered wrapper closes.Jira: https://issues.apache.org/jira/browse/THRIFT-6060
Testing
cmake -S . -B build -G Ninja -DBUILD_TESTING=ON cmake --build build --target UnitTests build/bin/UnitTests --run_test=OneWayHTTPTest --log_level=test_suite build/bin/UnitTestsRaw logs
On
upstream/master, with only the regression test applied:On
dfb55a2b8009537e55314d8a31f4130984ea4d9c, the exact-name regression fails:On this branch:
THRIFT-NNNNprefix.[skip ci]does not apply.