diff --git a/httplib.h b/httplib.h index 8c311403..7e0223e3 100644 --- a/httplib.h +++ b/httplib.h @@ -6321,6 +6321,39 @@ inline int shutdown_socket(socket_t sock) noexcept { #endif } +// Half-closes the write side and drains any in-flight/queued bytes before +// the final shutdown+close. Closing with unread data in the receive queue +// (or bytes arriving after the receive side is closed) makes the stack send +// an abortive RST instead of a graceful FIN, which can make the peer see the +// response as a failed read even though it was fully written. +inline void drain_and_close_socket(socket_t sock) noexcept { +#ifdef _WIN32 + shutdown(sock, SD_SEND); +#else + shutdown(sock, SHUT_WR); +#endif + + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + size_t total = 0; + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(100); // bound #1 + + while (total < size_t(1024u * 1024u)) { // bound #2 + const auto remaining = + std::chrono::duration_cast( + deadline - std::chrono::steady_clock::now()) + .count(); + if (remaining <= 0) { break; } + if (select_read(sock, 0, static_cast(remaining)) <= 0) { break; } + const auto n = read_socket(sock, buf, sizeof(buf), CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { break; } + total += static_cast(n); + } + + shutdown_socket(sock); + close_socket(sock); +} + inline std::string escape_abstract_namespace_unix_domain(const std::string &s) { if (s.size() > 1 && s[0] == '\0') { auto ret = s; @@ -13608,8 +13641,7 @@ inline bool Server::process_and_close_socket(socket_t sock) { nullptr, &websocket_upgraded); }); - detail::shutdown_socket(sock); - detail::close_socket(sock); + detail::drain_and_close_socket(sock); return ret; }