diff options
Diffstat (limited to 'src/httpserver.cpp')
-rw-r--r-- | src/httpserver.cpp | 65 |
1 files changed, 43 insertions, 22 deletions
diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 1a19555f76..942caa042d 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2015-2021 The Bitcoin Core developers +// Copyright (c) 2015-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -21,12 +21,14 @@ #include <util/threadnames.h> #include <util/translation.h> +#include <condition_variable> #include <cstdio> #include <cstdlib> #include <deque> #include <memory> #include <optional> #include <string> +#include <unordered_set> #include <sys/types.h> #include <sys/stat.h> @@ -34,6 +36,7 @@ #include <event2/buffer.h> #include <event2/bufferevent.h> #include <event2/http.h> +#include <event2/http_struct.h> #include <event2/keyvalq_struct.h> #include <event2/thread.h> #include <event2/util.h> @@ -146,6 +149,10 @@ static GlobalMutex g_httppathhandlers_mutex; static std::vector<HTTPPathHandler> pathHandlers GUARDED_BY(g_httppathhandlers_mutex); //! Bound listening sockets static std::vector<evhttp_bound_socket *> boundSockets; +//! Track active requests +static GlobalMutex g_requests_mutex; +static std::condition_variable g_requests_cv; +static std::unordered_set<evhttp_request*> g_requests GUARDED_BY(g_requests_mutex); /** Check if a network address is allowed to access the HTTP server */ static bool ClientAllowed(const CNetAddr& netaddr) @@ -192,24 +199,32 @@ std::string RequestMethodString(HTTPRequest::RequestMethod m) switch (m) { case HTTPRequest::GET: return "GET"; - break; case HTTPRequest::POST: return "POST"; - break; case HTTPRequest::HEAD: return "HEAD"; - break; case HTTPRequest::PUT: return "PUT"; - break; - default: + case HTTPRequest::UNKNOWN: return "unknown"; - } + } // no default case, so the compiler can warn about missing cases + assert(false); } /** HTTP request callback */ static void http_request_cb(struct evhttp_request* req, void* arg) { + // Track requests and notify when a request is completed. + { + WITH_LOCK(g_requests_mutex, g_requests.insert(req)); + g_requests_cv.notify_all(); + evhttp_request_set_on_complete_cb(req, [](struct evhttp_request* req, void*) { + auto n{WITH_LOCK(g_requests_mutex, return g_requests.erase(req))}; + assert(n == 1); + g_requests_cv.notify_all(); + }, nullptr); + } + // Disable reading to work around a libevent bug, fixed in 2.2.0. if (event_get_version_number() >= 0x02010600 && event_get_version_number() < 0x02020001) { evhttp_connection* conn = evhttp_request_get_connection(req); @@ -225,7 +240,7 @@ static void http_request_cb(struct evhttp_request* req, void* arg) // Early address-based allow check if (!ClientAllowed(hreq->GetPeer())) { LogPrint(BCLog::HTTP, "HTTP request from %s rejected: Client network is not allowed RPC access\n", - hreq->GetPeer().ToString()); + hreq->GetPeer().ToStringAddrPort()); hreq->WriteReply(HTTP_FORBIDDEN); return; } @@ -233,13 +248,13 @@ static void http_request_cb(struct evhttp_request* req, void* arg) // Early reject unknown HTTP methods if (hreq->GetRequestMethod() == HTTPRequest::UNKNOWN) { LogPrint(BCLog::HTTP, "HTTP request from %s rejected: Unknown HTTP request method\n", - hreq->GetPeer().ToString()); + hreq->GetPeer().ToStringAddrPort()); hreq->WriteReply(HTTP_BAD_METHOD); return; } LogPrint(BCLog::HTTP, "Received a %s request for %s from %s\n", - RequestMethodString(hreq->GetRequestMethod()), SanitizeString(hreq->GetURI(), SAFE_CHARS_URI).substr(0, 100), hreq->GetPeer().ToString()); + RequestMethodString(hreq->GetRequestMethod()), SanitizeString(hreq->GetURI(), SAFE_CHARS_URI).substr(0, 100), hreq->GetPeer().ToStringAddrPort()); // Find registered handler for prefix std::string strURI = hreq->GetURI(); @@ -282,7 +297,7 @@ static void http_reject_request_cb(struct evhttp_request* req, void*) } /** Event dispatcher thread */ -static bool ThreadHTTP(struct event_base* base) +static void ThreadHTTP(struct event_base* base) { util::ThreadRename("http"); SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET_HTTP_SERVER); @@ -290,7 +305,6 @@ static bool ThreadHTTP(struct event_base* base) event_base_dispatch(base); // Event loop will be interrupted by InterruptHTTPServer() LogPrint(BCLog::HTTP, "Exited http event loop\n"); - return event_base_got_break(base) == 0; } /** Bind HTTP server to specified addresses */ @@ -462,15 +476,27 @@ void StopHTTPServer() evhttp_del_accept_socket(eventHTTP, socket); } boundSockets.clear(); - if (eventBase) { - LogPrint(BCLog::HTTP, "Waiting for HTTP event thread to exit\n"); - if (g_thread_http.joinable()) g_thread_http.join(); + { + WAIT_LOCK(g_requests_mutex, lock); + if (!g_requests.empty()) { + LogPrint(BCLog::HTTP, "Waiting for %d requests to stop HTTP server\n", g_requests.size()); + } + g_requests_cv.wait(lock, []() EXCLUSIVE_LOCKS_REQUIRED(g_requests_mutex) { + return g_requests.empty(); + }); } if (eventHTTP) { - evhttp_free(eventHTTP); - eventHTTP = nullptr; + // Schedule a callback to call evhttp_free in the event base thread, so + // that evhttp_free does not need to be called again after the handling + // of unfinished request connections that follows. + event_base_once(eventBase, -1, EV_TIMEOUT, [](evutil_socket_t, short, void*) { + evhttp_free(eventHTTP); + eventHTTP = nullptr; + }, nullptr, nullptr); } if (eventBase) { + LogPrint(BCLog::HTTP, "Waiting for HTTP event thread to exit\n"); + if (g_thread_http.joinable()) g_thread_http.join(); event_base_free(eventBase); eventBase = nullptr; } @@ -626,19 +652,14 @@ HTTPRequest::RequestMethod HTTPRequest::GetRequestMethod() const switch (evhttp_request_get_command(req)) { case EVHTTP_REQ_GET: return GET; - break; case EVHTTP_REQ_POST: return POST; - break; case EVHTTP_REQ_HEAD: return HEAD; - break; case EVHTTP_REQ_PUT: return PUT; - break; default: return UNKNOWN; - break; } } |