aboutsummaryrefslogtreecommitdiff
path: root/src/httpserver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/httpserver.cpp')
-rw-r--r--src/httpserver.cpp65
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;
}
}