diff --git a/scripts/sync_vendor.py b/scripts/sync_vendor.py index e62eb2817b..2fb809a9f0 100755 --- a/scripts/sync_vendor.py +++ b/scripts/sync_vendor.py @@ -5,7 +5,7 @@ import os import sys import subprocess -HTTPLIB_VERSION = "refs/tags/v0.33.1" +HTTPLIB_VERSION = "refs/tags/v0.34.0" vendor = { "https://github.com/nlohmann/json/releases/latest/download/json.hpp": "vendor/nlohmann/json.hpp", diff --git a/vendor/cpp-httplib/httplib.cpp b/vendor/cpp-httplib/httplib.cpp index d929d2119e..15e118731f 100644 --- a/vendor/cpp-httplib/httplib.cpp +++ b/vendor/cpp-httplib/httplib.cpp @@ -1660,6 +1660,7 @@ public: bool is_readable() const override; bool wait_readable() const override; bool wait_writable() const override; + bool is_peer_alive() const override; ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; @@ -3313,10 +3314,10 @@ bool write_content_with_progress(Stream &strm, return ok; }; - data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); }; + data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); }; while (offset < end_offset && !is_shutting_down()) { - if (!strm.wait_writable()) { + if (!strm.wait_writable() || !strm.is_peer_alive()) { error = Error::Write; return false; } else if (!content_provider(offset, end_offset - offset, data_sink)) { @@ -3328,6 +3329,11 @@ bool write_content_with_progress(Stream &strm, } } + if (offset < end_offset) { // exited due to is_shutting_down(), not completion + error = Error::Write; + return false; + } + error = Error::Success; return true; } @@ -3367,12 +3373,12 @@ write_content_without_length(Stream &strm, return ok; }; - data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); }; + data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); }; data_sink.done = [&](void) { data_available = false; }; while (data_available && !is_shutting_down()) { - if (!strm.wait_writable()) { + if (!strm.wait_writable() || !strm.is_peer_alive()) { return false; } else if (!content_provider(offset, 0, data_sink)) { return false; @@ -3380,7 +3386,8 @@ write_content_without_length(Stream &strm, return false; } } - return true; + return !data_available; // true only if done() was called, false if shutting + // down } template @@ -3416,7 +3423,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider, return ok; }; - data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); }; + data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); }; auto done_with_trailer = [&](const Headers *trailer) { if (!ok) { return; } @@ -3466,7 +3473,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider, }; while (data_available && !is_shutting_down()) { - if (!strm.wait_writable()) { + if (!strm.wait_writable() || !strm.is_peer_alive()) { error = Error::Write; return false; } else if (!content_provider(offset, 0, data_sink)) { @@ -3478,6 +3485,11 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider, } } + if (data_available) { // exited due to is_shutting_down(), not done() + error = Error::Write; + return false; + } + error = Error::Success; return true; } @@ -4646,6 +4658,7 @@ public: bool is_readable() const override; bool wait_readable() const override; bool wait_writable() const override; + bool is_peer_alive() const override; ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; @@ -6069,8 +6082,11 @@ bool SocketStream::wait_readable() const { } bool SocketStream::wait_writable() const { - return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && - is_socket_alive(sock_); + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0; +} + +bool SocketStream::is_peer_alive() const { + return detail::is_socket_alive(sock_); } ssize_t SocketStream::read(char *ptr, size_t size) { @@ -6401,7 +6417,11 @@ bool SSLSocketStream::wait_readable() const { bool SSLSocketStream::wait_writable() const { return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && - is_socket_alive(sock_) && !tls::is_peer_closed(session_, sock_); + !tls::is_peer_closed(session_, sock_); +} + +bool SSLSocketStream::is_peer_alive() const { + return !tls::is_peer_closed(session_, sock_); } ssize_t SSLSocketStream::read(char *ptr, size_t size) { @@ -6925,35 +6945,33 @@ bool Server::write_response_core(Stream &strm, bool close_connection, if (post_routing_handler_) { post_routing_handler_(req, res); } // Response line and headers - { - detail::BufferStream bstrm; - if (!detail::write_response_line(bstrm, res.status)) { return false; } - if (header_writer_(bstrm, res.headers) <= 0) { return false; } + detail::BufferStream bstrm; + if (!detail::write_response_line(bstrm, res.status)) { return false; } + if (header_writer_(bstrm, res.headers) <= 0) { return false; } - // Flush buffer - auto &data = bstrm.get_buffer(); - detail::write_data(strm, data.data(), data.size()); + // Combine small body with headers to reduce write syscalls + if (req.method != "HEAD" && !res.body.empty() && !res.content_provider_) { + bstrm.write(res.body.data(), res.body.size()); } - // Body + // Log before writing to avoid race condition with client-side code that + // accesses logger-captured data immediately after receiving the response. + output_log(req, res); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { return false; } + + // Streaming body auto ret = true; - if (req.method != "HEAD") { - if (!res.body.empty()) { - if (!detail::write_data(strm, res.body.data(), res.body.size())) { - ret = false; - } - } else if (res.content_provider_) { - if (write_content_with_provider(strm, req, res, boundary, content_type)) { - res.content_provider_success_ = true; - } else { - ret = false; - } + if (req.method != "HEAD" && res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + ret = false; } } - // Log - output_log(req, res); - return ret; } diff --git a/vendor/cpp-httplib/httplib.h b/vendor/cpp-httplib/httplib.h index f2e3b69250..a39876891e 100644 --- a/vendor/cpp-httplib/httplib.h +++ b/vendor/cpp-httplib/httplib.h @@ -8,8 +8,8 @@ #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_VERSION "0.33.1" -#define CPPHTTPLIB_VERSION_NUM "0x002101" +#define CPPHTTPLIB_VERSION "0.34.0" +#define CPPHTTPLIB_VERSION_NUM "0x002200" /* * Platform compatibility check @@ -1038,6 +1038,32 @@ make_file_provider(const std::string &name, const std::string &filepath, return fdp; } +inline std::pair +make_file_body(const std::string &filepath) { + std::ifstream f(filepath, std::ios::binary | std::ios::ate); + if (!f) { return {0, ContentProvider{}}; } + auto size = static_cast(f.tellg()); + + ContentProvider provider = [filepath](size_t offset, size_t length, + DataSink &sink) -> bool { + std::ifstream f(filepath, std::ios::binary); + if (!f) { return false; } + f.seekg(static_cast(offset)); + if (!f.good()) { return false; } + char buf[8192]; + while (length > 0) { + auto to_read = (std::min)(sizeof(buf), length); + f.read(buf, static_cast(to_read)); + auto n = static_cast(f.gcount()); + if (n == 0) { break; } + if (!sink.write(buf, n)) { return false; } + length -= n; + } + return true; + }; + return {size, std::move(provider)}; +} + using ContentReceiverWithProgress = std::function; @@ -1352,6 +1378,7 @@ public: virtual bool is_readable() const = 0; virtual bool wait_readable() const = 0; virtual bool wait_writable() const = 0; + virtual bool is_peer_alive() const { return wait_writable(); } virtual ssize_t read(char *ptr, size_t size) = 0; virtual ssize_t write(const char *ptr, size_t size) = 0;