From 23c8f0c738aadfb5f61f4af3c2e8d844d85f3191 Mon Sep 17 00:00:00 2001 From: yhirose Date: Wed, 20 Dec 2017 17:28:36 -0500 Subject: [PATCH] Fixed #28. (Keep-Alive connection support) --- example/server.cc | 11 +- example/simplesvr.cc | 8 +- httplib.h | 346 ++++++++++++++++++++++++++++++------------- test/test.cc | 20 ++- 4 files changed, 263 insertions(+), 122 deletions(-) diff --git a/example/server.cc b/example/server.cc index a18bfc6..b8eb510 100644 --- a/example/server.cc +++ b/example/server.cc @@ -35,7 +35,7 @@ std::string log(const Request& req, const Response& res) s += "================================\n"; - snprintf(buf, sizeof(buf), "%s %s", req.method.c_str(), req.path.c_str()); + snprintf(buf, sizeof(buf), "%s %s %s", req.method.c_str(), req.path.c_str(), req.version.c_str()); s += buf; std::string query; @@ -52,9 +52,10 @@ std::string log(const Request& req, const Response& res) s += "--------------------------------\n"; - snprintf(buf, sizeof(buf), "%d\n", res.status); + snprintf(buf, sizeof(buf), "%d %s\n", res.status, res.version.c_str()); s += buf; s += dump_headers(res.headers); + s += "\n"; if (!res.body.empty()) { s += res.body; @@ -67,10 +68,12 @@ std::string log(const Request& req, const Response& res) int main(void) { + auto version = httplib::HttpVersion::v1_1; + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE); + SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, version); #else - Server svr; + Server svr(version); #endif if (!svr.is_valid()) { diff --git a/example/simplesvr.cc b/example/simplesvr.cc index 13b6b66..8a590a2 100644 --- a/example/simplesvr.cc +++ b/example/simplesvr.cc @@ -67,7 +67,7 @@ string log(const Request& req, const Response& res) s += "================================\n"; - snprintf(buf, sizeof(buf), "%s %s", req.method.c_str(), req.path.c_str()); + snprintf(buf, sizeof(buf), "%s %s %s", req.method.c_str(), req.path.c_str(), req.version.c_str()); s += buf; string query; @@ -99,10 +99,12 @@ int main(int argc, const char** argv) return 1; } + auto version = httplib::HttpVersion::v1_1; + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE); + SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, version); #else - Server svr; + Server svr(version); #endif svr.post("/multipart", [](const auto& req, auto& res) { diff --git a/httplib.h b/httplib.h index 8630559..996dd92 100644 --- a/httplib.h +++ b/httplib.h @@ -2,7 +2,7 @@ // httplib.h // // Copyright (c) 2017 Yuji Hirose. All rights reserved. -// The Boost Software License 1.0 +// MIT License // #ifndef _CPPHTTPLIB_HTTPLIB_H_ @@ -63,6 +63,13 @@ typedef int socket_t; #include #endif +/* + * Configuration + */ +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND 0 + namespace httplib { @@ -74,7 +81,7 @@ struct ci { s1.begin(), s1.end(), s2.begin(), s2.end(), [](char c1, char c2) { - return std::tolower(c1) < std::tolower(c2); + return ::tolower(c1) < ::tolower(c2); }); } }; @@ -88,8 +95,8 @@ typedef std::multimap Headers; template std::pair make_range_header(uint64_t value, Args... args); -typedef std::multimap Params; -typedef std::smatch Match; +typedef std::multimap Params; +typedef std::smatch Match; typedef std::function Progress; struct MultipartFile { @@ -101,6 +108,7 @@ struct MultipartFile { typedef std::multimap MultipartFiles; struct Request { + std::string version; std::string method; std::string path; Headers headers; @@ -113,6 +121,7 @@ struct Request { bool has_header(const char* key) const; std::string get_header_value(const char* key) const; + void set_header(const char* key, const char* val); bool has_param(const char* key) const; std::string get_param_value(const char* key) const; @@ -122,6 +131,7 @@ struct Request { }; struct Response { + std::string version; int status; Headers headers; std::string body; @@ -166,7 +176,8 @@ public: typedef std::function Handler; typedef std::function Logger; - Server(); + Server(HttpVersion http_version = HttpVersion::v1_0); + virtual ~Server(); virtual bool is_valid() const; @@ -180,10 +191,14 @@ public: void set_logger(Logger logger); bool listen(const char* host, int port, int socket_flags = 0); + + bool is_running() const; void stop(); protected: - void process_request(Stream& strm); + bool process_request(Stream& strm, bool last_connection); + + const HttpVersion http_version_; private: typedef std::vector> Handlers; @@ -192,8 +207,8 @@ private: bool handle_file_request(Request& req, Response& res); bool dispatch_request(Request& req, Response& res, Handlers& handlers); - bool read_request_line(Stream& strm, Request& req); - void write_response(Stream& strm, const Request& req, Response& res); + bool parse_request_line(const char* s, Request& req); + void write_response(Stream& strm, bool last_connection, const Request& req, Response& res); virtual bool read_and_close_socket(socket_t sock); @@ -224,10 +239,10 @@ public: std::shared_ptr post(const char* path, const Params& params); std::shared_ptr post(const char* path, const Headers& headers, const Params& params); - bool send(const Request& req, Response& res); + bool send(Request& req, Response& res); protected: - bool process_request(Stream& strm, const Request& req, Response& res); + bool process_request(Stream& strm, Request& req, Response& res); const std::string host_; const int port_; @@ -236,9 +251,9 @@ protected: private: bool read_response_line(Stream& strm, Response& res); - void write_request(Stream& strm, const Request& req, const char* ver); + void write_request(Stream& strm, Request& req); - virtual bool read_and_close_socket(socket_t sock, const Request& req, Response& res); + virtual bool read_and_close_socket(socket_t sock, Request& req, Response& res); }; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -257,7 +272,10 @@ private: class SSLServer : public Server { public: - SSLServer(const char* cert_path, const char* private_key_path); + SSLServer( + const char* cert_path, const char* private_key_path, + HttpVersion http_version = HttpVersion::v1_0); + virtual ~SSLServer(); virtual bool is_valid() const; @@ -276,7 +294,7 @@ public: virtual bool is_valid() const; private: - virtual bool read_and_close_socket(socket_t sock, const Request& req, Response& res); + virtual bool read_and_close_socket(socket_t sock, Request& req, Response& res); SSL_CTX* ctx_; }; @@ -330,13 +348,13 @@ public: fixed_buffer_used_size_ = 0; glowable_buffer_.clear(); - size_t i = 0; - - for (;;) { + for (size_t i = 0; ; i++) { char byte; auto n = strm_.read(&byte, 1); - if (n < 1) { + if (n < 0) { + return false; + } else if (n == 0) { if (i == 0) { return false; } else { @@ -351,17 +369,18 @@ public: } } - append('\0'); return true; } private: void append(char c) { - if (fixed_buffer_used_size_ < fixed_buffer_size_) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; } else { if (glowable_buffer_.empty()) { - glowable_buffer_.assign(fixed_buffer_, fixed_buffer_size_); + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); } glowable_buffer_ += c; } @@ -383,11 +402,43 @@ inline int close_socket(socket_t sock) #endif } -template -inline bool read_and_close_socket(socket_t sock, T callback) +inline int select(socket_t sock, size_t sec, size_t usec) { - SocketStream strm(sock); - auto ret = callback(strm); + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = sec; + tv.tv_usec = usec; + + return ::select(sock + 1, &fds, NULL, NULL, &tv); +} + +template +inline bool read_and_close_socket(socket_t sock, bool keep_alive, T callback) +{ + bool ret = false; + + if (keep_alive) { + auto count = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + while (count > 0 && + detail::select(sock, + CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, + CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0) { + auto last_connection = count == 1; + SocketStream strm(sock); + ret = callback(strm, last_connection); + if (!ret) { + break; + } + count--; + } + } else { + SocketStream strm(sock); + ret = callback(strm, true); + } + close_socket(sock); return ret; } @@ -641,12 +692,12 @@ bool read_content_with_length(Stream& strm, T& x, size_t len, Progress progress) x.body.assign(len, 0); size_t r = 0; while (r < len){ - auto r_incr = strm.read(&x.body[r], len - r); - if (r_incr <= 0) { + auto n = strm.read(&x.body[r], len - r); + if (n <= 0) { return false; } - r += r_incr; + r += n; if (progress) { progress(r, len); @@ -665,7 +716,7 @@ bool read_content_without_length(Stream& strm, T& x) if (n < 0) { return false; } else if (n == 0) { - break; + return true; } x.body += byte; } @@ -738,17 +789,10 @@ bool read_content(Stream& strm, T& x, Progress progress = Progress()) template inline void write_headers(Stream& strm, const T& info) { - strm.write("Connection: close\r\n"); - for (const auto& x: info.headers) { - if (x.first != "Content-Type" && x.first != "Content-Length") { - strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); - } + strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); } - - auto t = get_header_value(info.headers, "Content-Type", "text/plain"); - strm.write_format("Content-Type: %s\r\n", t); - strm.write_format("Content-Length: %ld\r\n", info.body.size()); + strm.write("\r\n"); } inline std::string encode_url(const std::string& s) @@ -1065,6 +1109,11 @@ inline std::string Request::get_header_value(const char* key) const return detail::get_header_value(headers, key, ""); } +inline void Request::set_header(const char* key, const char* val) +{ + headers.emplace(key, val); +} + inline bool Request::has_param(const char* key) const { return params.find(key) != params.end(); @@ -1134,7 +1183,11 @@ inline void Stream::write_format(const char* fmt, const Args& ...args) const auto bufsiz = 2048; char buf[bufsiz]; +#if defined(_MSC_VER) && _MSC_VER < 1900 + auto n = _snprintf_s(buf, bufsiz, bufsiz - 1, fmt, args...); +#else auto n = snprintf(buf, bufsiz - 1, fmt, args...); +#endif if (n > 0) { if (n >= bufsiz - 1) { std::vector glowable_buf(bufsiz); @@ -1179,8 +1232,9 @@ inline int SocketStream::write(const char* ptr) } // HTTP server implementation -inline Server::Server() - : svr_sock_(-1) +inline Server::Server(HttpVersion http_version) + : http_version_(http_version) + , svr_sock_(-1) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); @@ -1236,6 +1290,16 @@ inline bool Server::listen(const char* host, int port, int socket_flags) auto ret = true; for (;;) { + auto val = detail::select(svr_sock_, 0, 100000); + + if (val == 0) { // Timeout + if (svr_sock_ == -1) { + // The server socket was closed by 'stop' method. + break; + } + continue; + } + socket_t sock = accept(svr_sock_, NULL, NULL); if (sock == -1) { @@ -1248,19 +1312,20 @@ inline bool Server::listen(const char* host, int port, int socket_flags) break; } - // TODO: should be async -#ifdef CPPHTTPLIB_NO_MULTI_THREAD_SUPPORT - read_and_close_socket(sock); -#else + // TODO: Use thread pool... std::thread([=]() { read_and_close_socket(sock); }).detach(); -#endif } return ret; } +inline bool Server::is_running() const +{ + return svr_sock_ != -1; +} + inline void Server::stop() { detail::shutdown_socket(svr_sock_); @@ -1268,21 +1333,13 @@ inline void Server::stop() svr_sock_ = -1; } -inline bool Server::read_request_line(Stream& strm, Request& req) +inline bool Server::parse_request_line(const char* s, Request& req) { - const auto bufsiz = 2048; - char buf[bufsiz]; - - detail::stream_line_reader reader(strm, buf, bufsiz); - - if (!reader.getline()) { - return false; - } - - static std::regex re("(GET|HEAD|POST) ([^?]+)(?:\\?(.+?))? HTTP/1\\.[01]\r\n"); + static std::regex re("(GET|HEAD|POST) ([^?]+)(?:\\?(.+?))? (HTTP/1\\.[01])\r\n"); std::cmatch m; - if (std::regex_match(reader.ptr(), m, re)) { + if (std::regex_match(s, m, re)) { + req.version = std::string(m[4]); req.method = std::string(m[1]); req.path = detail::decode_url(m[2]); @@ -1298,7 +1355,7 @@ inline bool Server::read_request_line(Stream& strm, Request& req) return false; } -inline void Server::write_response(Stream& strm, const Request& req, Response& res) +inline void Server::write_response(Stream& strm, bool last_connection, const Request& req, Response& res) { assert(res.status != -1); @@ -1306,17 +1363,34 @@ inline void Server::write_response(Stream& strm, const Request& req, Response& r error_handler_(req, res); } - strm.write_format( - "HTTP/1.0 %d %s\r\n", - res.status, detail::status_message(res.status)); + // Response line + strm.write_format("%s %d %s\r\n", + detail::http_version_strings[static_cast(http_version_)], + res.status, + detail::status_message(res.status)); + + // Headers + if (!res.has_header("Connection") && (last_connection || req.version == "HTTP/1.0")) { + res.set_header("Connection", "close"); + } + + if (!res.body.empty()) { + if (!res.has_header("Content-Type")) { + res.set_header("Content-Type", "application/octet-stream"); + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length.c_str()); + } detail::write_headers(strm, res); - strm.write("\r\n"); + // Body if (!res.body.empty() && req.method != "HEAD") { strm.write(res.body.c_str(), res.body.size()); } + // Log if (logger_) { logger_(req, res); } @@ -1373,22 +1447,41 @@ inline bool Server::dispatch_request(Request& req, Response& res, Handlers& hand return false; } -inline void Server::process_request(Stream& strm) +inline bool Server::process_request(Stream& strm, bool last_connection) { + const auto bufsiz = 2048; + char buf[bufsiz]; + + detail::stream_line_reader reader(strm, buf, bufsiz); + + // Connection has been closed on client + if (!reader.getline()) { + return false; + } + Request req; Response res; - if (!read_request_line(strm, req) || !detail::read_headers(strm, req.headers)) { + res.version = detail::http_version_strings[static_cast(http_version_)]; + + // Request line and headers + if (!parse_request_line(reader.ptr(), req) || !detail::read_headers(strm, req.headers)) { res.status = 400; - write_response(strm, req, res); - return; + write_response(strm, last_connection, req, res); + return true; } + auto ret = true; + if (req.get_header_value("Connection") == "close") { + ret = false; + } + + // Body if (req.method == "POST") { if (!detail::read_content(strm, req)) { res.status = 400; - write_response(strm, req, res); - return; + write_response(strm, last_connection, req, res); + return ret; } const auto& content_type = req.get_header_value("Content-Type"); @@ -1400,8 +1493,8 @@ inline void Server::process_request(Stream& strm) if (!detail::parse_multipart_boundary(content_type, boundary) || !detail::parse_multipart_formdata(boundary, req.body, req.files)) { res.status = 400; - write_response(strm, req, res); - return; + write_response(strm, last_connection, req, res); + return ret; } } } @@ -1414,7 +1507,8 @@ inline void Server::process_request(Stream& strm) res.status = 404; } - write_response(strm, req, res); + write_response(strm, last_connection, req, res); + return ret; } inline bool Server::is_valid() const @@ -1424,10 +1518,14 @@ inline bool Server::is_valid() const inline bool Server::read_and_close_socket(socket_t sock) { - return detail::read_and_close_socket(sock, [this](Stream& strm) { - process_request(strm); - return true; - }); + auto keep_alive = http_version_ == HttpVersion::v1_1; + + return detail::read_and_close_socket( + sock, + keep_alive, + [this](Stream& strm, bool last_connection) { + return process_request(strm, last_connection); + }); } // HTTP client implementation @@ -1469,7 +1567,7 @@ inline bool Client::read_response_line(Stream& strm, Response& res) return true; } -inline bool Client::send(const Request& req, Response& res) +inline bool Client::send(Request& req, Response& res) { if (req.path.empty()) { return false; @@ -1483,29 +1581,43 @@ inline bool Client::send(const Request& req, Response& res) return read_and_close_socket(sock, req, res); } -inline void Client::write_request(Stream& strm, const Request& req, const char* ver) +inline void Client::write_request(Stream& strm, Request& req) { auto path = detail::encode_url(req.path); // Request line - strm.write_format( - "%s %s %s\r\n", req.method.c_str(), path.c_str(), ver); + strm.write_format("%s %s %s\r\n", + req.method.c_str(), + path.c_str(), + detail::http_version_strings[static_cast(http_version_)]); // Headers - strm.write_format("Host: %s\r\n", host_and_port_.c_str()); + req.set_header("Host", host_and_port_.c_str()); if (!req.has_header("Accept")) { - strm.write("Accept: */*\r\n"); + req.set_header("Accept", "*/*"); } if (!req.has_header("User-Agent")) { - strm.write("User-Agent: cpp-httplib/0.1\r\n"); + req.set_header("User-Agent", "cpp-httplib/0.2"); + } + + // TODO: if (!req.has_header("Connection") && (last_connection || http_version_ == detail::HttpVersion::v1_0)) { + if (!req.has_header("Connection")) { + req.set_header("Connection", "close"); + } + + if (!req.body.empty()) { + if (!req.has_header("Content-Type")) { + req.set_header("Content-Type", "application/octet-stream"); + } + + auto length = std::to_string(req.body.size()); + req.set_header("Content-Length", length.c_str()); } detail::write_headers(strm, req); - strm.write("\r\n"); - // Body if (!req.body.empty()) { if (req.has_header("application/x-www-form-urlencoded")) { @@ -1517,18 +1629,19 @@ inline void Client::write_request(Stream& strm, const Request& req, const char* } } -inline bool Client::process_request(Stream& strm, const Request& req, Response& res) +inline bool Client::process_request(Stream& strm, Request& req, Response& res) { // Send request - auto ver = detail::http_version_strings[static_cast(http_version_)]; - write_request(strm, req, ver); + write_request(strm, req); - // Receive response - if (!read_response_line(strm, res) || - !detail::read_headers(strm, res.headers)) { + // Receive response and headers + if (!read_response_line(strm, res) || !detail::read_headers(strm, res.headers)) { return false; } + // TODO: Check if 'Connection' header is 'close' or HTTP version is 1.0, then close socket... + + // Body if (req.method != "HEAD") { if (!detail::read_content(strm, res, req.progress)) { return false; @@ -1538,9 +1651,9 @@ inline bool Client::process_request(Stream& strm, const Request& req, Response& return true; } -inline bool Client::read_and_close_socket(socket_t sock, const Request& req, Response& res) +inline bool Client::read_and_close_socket(socket_t sock, Request& req, Response& res) { - return detail::read_and_close_socket(sock, [&](Stream& strm) { + return detail::read_and_close_socket(sock, false, [&](Stream& strm, bool /*last_connection*/) { return process_request(strm, req, res); }); } @@ -1629,7 +1742,10 @@ inline std::shared_ptr Client::post(const char* path, const Headers& h namespace detail { template -inline bool read_and_close_socket_ssl(socket_t sock, SSL_CTX* ctx, U SSL_connect_or_accept, V setup, T callback) +inline bool read_and_close_socket_ssl( + socket_t sock, bool keep_alive, + SSL_CTX* ctx, U SSL_connect_or_accept, V setup, + T callback) { auto ssl = SSL_new(ctx); if (!ssl) { @@ -1643,8 +1759,26 @@ inline bool read_and_close_socket_ssl(socket_t sock, SSL_CTX* ctx, U SSL_connect SSL_connect_or_accept(ssl); - SSLSocketStream strm(ssl); - auto ret = callback(strm); + bool ret = false; + + if (keep_alive) { + auto count = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + while (count > 0 && + detail::select(sock, + CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, + CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0) { + auto last_connection = count == 1; + SSLSocketStream strm(ssl); + ret = callback(strm, last_connection); + if (!ret) { + break; + } + count--; + } + } else { + SSLSocketStream strm(ssl); + ret = callback(strm, true); + } SSL_shutdown(ssl); SSL_free(ssl); @@ -1689,7 +1823,8 @@ inline int SSLSocketStream::write(const char* ptr) } // SSL HTTP server implementation -inline SSLServer::SSLServer(const char* cert_path, const char* private_key_path) +inline SSLServer::SSLServer(const char* cert_path, const char* private_key_path, HttpVersion http_version) + : Server(http_version) { ctx_ = SSL_CTX_new(SSLv23_server_method()); @@ -1725,13 +1860,16 @@ inline bool SSLServer::is_valid() const inline bool SSLServer::read_and_close_socket(socket_t sock) { + auto keep_alive = http_version_ == HttpVersion::v1_1; + return detail::read_and_close_socket_ssl( - sock, ctx_, + sock, + keep_alive, + ctx_, SSL_accept, [](SSL* /*ssl*/) {}, - [this](Stream& strm) { - process_request(strm); - return true; + [this](Stream& strm, bool last_connection) { + return process_request(strm, last_connection); }); } @@ -1754,15 +1892,15 @@ inline bool SSLClient::is_valid() const return ctx_; } -inline bool SSLClient::read_and_close_socket(socket_t sock, const Request& req, Response& res) +inline bool SSLClient::read_and_close_socket(socket_t sock, Request& req, Response& res) { return is_valid() && detail::read_and_close_socket_ssl( - sock, ctx_, - SSL_connect, + sock, false, + ctx_, SSL_connect, [&](SSL* ssl) { SSL_set_tlsext_host_name(ssl, host_.c_str()); }, - [&](Stream& strm) { + [&](Stream& strm, bool /*last_connection*/) { return process_request(strm, req, res); }); } diff --git a/test/test.cc b/test/test.cc index d1996da..8db9373 100644 --- a/test/test.cc +++ b/test/test.cc @@ -139,11 +139,12 @@ TEST(GetHeaderValueTest, Range) void testChunkedEncoding(httplib::HttpVersion ver) { auto host = "www.httpwatch.com"; - auto port = 80; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT + auto port = 443; httplib::SSLClient cli(host, port, ver); #else + auto port = 80; httplib::Client cli(host, port, ver); #endif @@ -166,11 +167,12 @@ TEST(ChunkedEncodingTest, FromHTTPWatch) TEST(RangeTest, FromHTTPBin) { auto host = "httpbin.org"; - auto port = 80; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT + auto port = 443; httplib::SSLClient cli(host, port, httplib::HttpVersion::v1_1); #else + auto port = 80; httplib::Client cli(host, port, httplib::HttpVersion::v1_1); #endif @@ -265,27 +267,23 @@ protected: EXPECT_EQ("application/octet-stream", file.content_type); EXPECT_EQ(0u, file.length); } - }) - .get("/stop", [&](const Request& /*req*/, Response& /*res*/) { - svr_.stop(); }); persons_["john"] = "programmer"; - f_ = async([&](){ + t_ = thread([&](){ up_ = true; svr_.listen(HOST, PORT); }); - while (!up_) { + while (!svr_.is_running()) { msleep(1); } } virtual void TearDown() { - //svr_.stop(); // NOTE: This causes dead lock on Windows. - cli_.get("/stop"); - f_.get(); + svr_.stop(); + t_.join(); } map persons_; @@ -296,7 +294,7 @@ protected: Client cli_; Server svr_; #endif - future f_; + thread t_; bool up_; };