|
|
@@ -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 close_socket_gracefully(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<std::chrono::microseconds>(
|
|
|
+ deadline - std::chrono::steady_clock::now())
|
|
|
+ .count();
|
|
|
+ if (remaining <= 0) { break; }
|
|
|
+ if (select_read(sock, 0, static_cast<time_t>(remaining)) <= 0) { break; }
|
|
|
+ const auto n = read_socket(sock, buf, sizeof(buf), CPPHTTPLIB_RECV_FLAGS);
|
|
|
+ if (n <= 0) { break; }
|
|
|
+ total += static_cast<size_t>(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::close_socket_gracefully(sock);
|
|
|
return ret;
|
|
|
}
|
|
|
|