mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-26 12:24:46 +00:00
Open a new connection for a redirect after sending Connection: close (#2645)
Without keep-alive, the client sends "Connection: close", but before following a redirect it disconnected only when the response repeated the option. RFC 9112 (section 9.6) lets a server close the connection without repeating it, and a client that sent "close" must not send another request on that connection. The redirected request was written to the closing connection and failed with "Failed to read connection" whenever the server closed it first. The client now disconnects before a redirect or an auth retry whenever the request asked for the close. Co-authored-by: Wleeaf <179125857+wleeaf@users.noreply.github.com>
This commit is contained in:
1 parent
a210b43e11
commit
5ceef83c54
2 files changed
+73
-3
No files matched your search
@@ -16278,9 +16278,10 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
|||||||
// to call it from a different thread since it's a thread-safety issue
|
// to call it from a different thread since it's a thread-safety issue
|
||||||
// to do these things to the socket if another thread is using the socket.
|
// to do these things to the socket if another thread is using the socket.
|
||||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||||
// Also when the request asked for it (e.g. a body that was never sent),
|
// Also when the request asked for it (e.g. a body that was never sent,
|
||||||
// so that a redirect or an auth retry below does not reuse the socket.
|
// or "Connection: close"), so that a redirect or an auth retry below does
|
||||||
if (socket_should_be_closed_when_request_is_done_ ||
|
// not reuse the socket.
|
||||||
|
if (socket_should_be_closed_when_request_is_done_ || close_connection ||
|
||||||
detail::has_header_token(res.headers, "Connection", "close") ||
|
detail::has_header_token(res.headers, "Connection", "close") ||
|
||||||
(res.version == "HTTP/1.0" && res.reason != "Connection established")) {
|
(res.version == "HTTP/1.0" && res.reason != "Connection established")) {
|
||||||
disconnect(/*gracefully=*/true);
|
disconnect(/*gracefully=*/true);
|
||||||
|
|||||||
@@ -4306,6 +4306,75 @@ TEST(RedirectFromPageWithContent, Redirect) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A server can close the connection after a request with "Connection: close"
|
||||||
|
// without repeating the option in its response (RFC 9112 Section 9.6), so the
|
||||||
|
// redirected request must go out on a new connection.
|
||||||
|
TEST(RedirectTest, NewConnectionAfterConnectionClose) {
|
||||||
|
#ifndef _WIN32
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto srv = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
default_socket_options(srv);
|
||||||
|
|
||||||
|
sockaddr_in addr{};
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(static_cast<uint16_t>(PORT + 2));
|
||||||
|
::inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
|
||||||
|
ASSERT_EQ(0, ::bind(srv, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
|
||||||
|
ASSERT_EQ(0, ::listen(srv, 2));
|
||||||
|
|
||||||
|
auto send_text = [](socket_t sock, const std::string &text) {
|
||||||
|
::send(sock,
|
||||||
|
#ifdef _WIN32
|
||||||
|
static_cast<const char *>(text.c_str()),
|
||||||
|
static_cast<int>(text.size()),
|
||||||
|
#else
|
||||||
|
text.c_str(), text.size(),
|
||||||
|
#endif
|
||||||
|
0);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto reused = false;
|
||||||
|
auto server_thread = std::thread([&] {
|
||||||
|
char buf[4096];
|
||||||
|
auto first = ::accept(srv, nullptr, nullptr);
|
||||||
|
if (first == INVALID_SOCKET) { return; }
|
||||||
|
detail::set_socket_opt_time(first, SOL_SOCKET, SO_RCVTIMEO, 5, 0);
|
||||||
|
::recv(first, buf, sizeof(buf), 0);
|
||||||
|
send_text(
|
||||||
|
first,
|
||||||
|
"HTTP/1.1 302 Found\r\nLocation: /2\r\nContent-Length: 0\r\n\r\n");
|
||||||
|
|
||||||
|
auto next = first;
|
||||||
|
if (::recv(first, buf, sizeof(buf), 0) > 0) {
|
||||||
|
reused = true;
|
||||||
|
} else {
|
||||||
|
detail::close_socket(first);
|
||||||
|
next = ::accept(srv, nullptr, nullptr);
|
||||||
|
if (next == INVALID_SOCKET) { return; }
|
||||||
|
detail::set_socket_opt_time(next, SOL_SOCKET, SO_RCVTIMEO, 5, 0);
|
||||||
|
::recv(next, buf, sizeof(buf), 0);
|
||||||
|
}
|
||||||
|
send_text(next, "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n"
|
||||||
|
"Connection: close\r\n\r\nok");
|
||||||
|
detail::close_socket(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
Client cli("127.0.0.1", PORT + 2);
|
||||||
|
cli.set_follow_location(true);
|
||||||
|
cli.set_read_timeout(5, 0);
|
||||||
|
auto res = cli.Get("/1");
|
||||||
|
|
||||||
|
server_thread.join();
|
||||||
|
detail::close_socket(srv);
|
||||||
|
|
||||||
|
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
EXPECT_EQ("ok", res->body);
|
||||||
|
EXPECT_FALSE(reused);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(RedirectFromPageWithContentIP6, Redirect) {
|
TEST(RedirectFromPageWithContentIP6, Redirect) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user