Changeset View
Changeset View
Standalone View
Standalone View
src/httpserver.cpp
// Copyright (c) 2015-2016 The Bitcoin Core developers | // Copyright (c) 2015-2016 The Bitcoin Core developers | ||||
// Copyright (c) 2018 The Bitcoin developers | // Copyright (c) 2018 The Bitcoin developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include <httpserver.h> | #include <httpserver.h> | ||||
#include <chainparamsbase.h> | #include <chainparamsbase.h> | ||||
#include <compat.h> | #include <compat.h> | ||||
#include <config.h> | #include <config.h> | ||||
#include <logging.h> | #include <logging.h> | ||||
#include <netbase.h> | #include <netbase.h> | ||||
#include <rpc/protocol.h> // For HTTP status codes | #include <rpc/protocol.h> // For HTTP status codes | ||||
#include <shutdown.h> | |||||
#include <sync.h> | #include <sync.h> | ||||
#include <ui_interface.h> | #include <ui_interface.h> | ||||
#include <util/strencodings.h> | #include <util/strencodings.h> | ||||
#include <util/system.h> | #include <util/system.h> | ||||
#include <event2/buffer.h> | #include <event2/buffer.h> | ||||
#include <event2/bufferevent.h> | #include <event2/bufferevent.h> | ||||
#include <event2/keyvalq_struct.h> | #include <event2/keyvalq_struct.h> | ||||
Show All 12 Lines | |||||
#include <sys/stat.h> | #include <sys/stat.h> | ||||
#include <sys/types.h> | #include <sys/types.h> | ||||
#include <csignal> | #include <csignal> | ||||
#include <cstdio> | #include <cstdio> | ||||
#include <cstdlib> | #include <cstdlib> | ||||
#include <cstring> | #include <cstring> | ||||
#include <deque> | #include <deque> | ||||
#include <future> | |||||
#include <memory> | #include <memory> | ||||
/** Maximum size of http request (request line + headers) */ | /** Maximum size of http request (request line + headers) */ | ||||
static const size_t MAX_HEADERS_SIZE = 8192; | static const size_t MAX_HEADERS_SIZE = 8192; | ||||
/** | /** | ||||
* Maximum HTTP post body size. Twice the maximum block size is added to this | * Maximum HTTP post body size. Twice the maximum block size is added to this | ||||
* value in practice. | * value in practice. | ||||
▲ Show 20 Lines • Show All 395 Lines • ▼ Show 20 Lines | #if LIBEVENT_VERSION_NUMBER >= 0x02010100 | ||||
return true; | return true; | ||||
#else | #else | ||||
// Can't update libevent logging if version < 02010100 | // Can't update libevent logging if version < 02010100 | ||||
return false; | return false; | ||||
#endif | #endif | ||||
} | } | ||||
std::thread threadHTTP; | std::thread threadHTTP; | ||||
std::future<bool> threadResult; | |||||
static std::vector<std::thread> g_thread_http_workers; | static std::vector<std::thread> g_thread_http_workers; | ||||
void StartHTTPServer() { | void StartHTTPServer() { | ||||
LogPrint(BCLog::HTTP, "Starting HTTP server\n"); | LogPrint(BCLog::HTTP, "Starting HTTP server\n"); | ||||
int rpcThreads = | int rpcThreads = | ||||
std::max((long)gArgs.GetArg("-rpcthreads", DEFAULT_HTTP_THREADS), 1L); | std::max((long)gArgs.GetArg("-rpcthreads", DEFAULT_HTTP_THREADS), 1L); | ||||
LogPrintf("HTTP: starting %d worker threads\n", rpcThreads); | LogPrintf("HTTP: starting %d worker threads\n", rpcThreads); | ||||
std::packaged_task<bool(event_base *)> task(ThreadHTTP); | threadHTTP = std::thread(ThreadHTTP, eventBase); | ||||
threadResult = task.get_future(); | |||||
threadHTTP = std::thread(std::move(task), eventBase); | |||||
for (int i = 0; i < rpcThreads; i++) { | for (int i = 0; i < rpcThreads; i++) { | ||||
g_thread_http_workers.emplace_back(HTTPWorkQueueRun, workQueue); | g_thread_http_workers.emplace_back(HTTPWorkQueueRun, workQueue); | ||||
} | } | ||||
} | } | ||||
void InterruptHTTPServer() { | void InterruptHTTPServer() { | ||||
LogPrint(BCLog::HTTP, "Interrupting HTTP server\n"); | LogPrint(BCLog::HTTP, "Interrupting HTTP server\n"); | ||||
if (eventHTTP) { | if (eventHTTP) { | ||||
// Unlisten sockets | |||||
for (evhttp_bound_socket *socket : boundSockets) { | |||||
evhttp_del_accept_socket(eventHTTP, socket); | |||||
} | |||||
// Reject requests on current connections | // Reject requests on current connections | ||||
evhttp_set_gencb(eventHTTP, http_reject_request_cb, nullptr); | evhttp_set_gencb(eventHTTP, http_reject_request_cb, nullptr); | ||||
} | } | ||||
if (workQueue) { | if (workQueue) { | ||||
workQueue->Interrupt(); | workQueue->Interrupt(); | ||||
} | } | ||||
} | } | ||||
void StopHTTPServer() { | void StopHTTPServer() { | ||||
LogPrint(BCLog::HTTP, "Stopping HTTP server\n"); | LogPrint(BCLog::HTTP, "Stopping HTTP server\n"); | ||||
if (workQueue) { | if (workQueue) { | ||||
LogPrint(BCLog::HTTP, "Waiting for HTTP worker threads to exit\n"); | LogPrint(BCLog::HTTP, "Waiting for HTTP worker threads to exit\n"); | ||||
for (auto &thread : g_thread_http_workers) { | for (auto &thread : g_thread_http_workers) { | ||||
thread.join(); | thread.join(); | ||||
} | } | ||||
g_thread_http_workers.clear(); | g_thread_http_workers.clear(); | ||||
delete workQueue; | delete workQueue; | ||||
workQueue = nullptr; | workQueue = nullptr; | ||||
} | } | ||||
// Unlisten sockets, these are what make the event loop running, which means | |||||
// that after this and all connections are closed the event loop will quit. | |||||
for (evhttp_bound_socket *socket : boundSockets) { | |||||
evhttp_del_accept_socket(eventHTTP, socket); | |||||
} | |||||
boundSockets.clear(); | |||||
if (eventBase) { | if (eventBase) { | ||||
LogPrint(BCLog::HTTP, "Waiting for HTTP event thread to exit\n"); | LogPrint(BCLog::HTTP, "Waiting for HTTP event thread to exit\n"); | ||||
// Exit the event loop as soon as there are no active events. | |||||
event_base_loopexit(eventBase, nullptr); | |||||
// Give event loop a few seconds to exit (to send back last RPC | |||||
// responses), then break it. Before this was solved with | |||||
// event_base_loopexit, but that didn't work as expected in at least | |||||
// libevent 2.0.21 and always introduced a delay. In libevent master | |||||
// that appears to be solved, so in the future that solution could be | |||||
// used again (if desirable). | |||||
// (see discussion in https://github.com/bitcoin/bitcoin/pull/6990) | |||||
if (threadResult.valid() && | |||||
threadResult.wait_for(std::chrono::milliseconds(2000)) == | |||||
std::future_status::timeout) { | |||||
LogPrintf("HTTP event loop did not exit within allotted time, " | |||||
"sending loopbreak\n"); | |||||
event_base_loopbreak(eventBase); | |||||
} | |||||
threadHTTP.join(); | threadHTTP.join(); | ||||
} | } | ||||
if (eventHTTP) { | if (eventHTTP) { | ||||
evhttp_free(eventHTTP); | evhttp_free(eventHTTP); | ||||
eventHTTP = nullptr; | eventHTTP = nullptr; | ||||
} | } | ||||
if (eventBase) { | if (eventBase) { | ||||
event_base_free(eventBase); | event_base_free(eventBase); | ||||
▲ Show 20 Lines • Show All 89 Lines • ▼ Show 20 Lines | |||||
/** | /** | ||||
* Closure sent to main thread to request a reply to be sent to a HTTP request. | * Closure sent to main thread to request a reply to be sent to a HTTP request. | ||||
* Replies must be sent in the main loop in the main http thread, this cannot be | * Replies must be sent in the main loop in the main http thread, this cannot be | ||||
* done from worker threads. | * done from worker threads. | ||||
*/ | */ | ||||
void HTTPRequest::WriteReply(int nStatus, const std::string &strReply) { | void HTTPRequest::WriteReply(int nStatus, const std::string &strReply) { | ||||
assert(!replySent && req); | assert(!replySent && req); | ||||
if (ShutdownRequested()) { | |||||
WriteHeader("Connection", "close"); | |||||
} | |||||
// Send event to main http thread to send reply message | // Send event to main http thread to send reply message | ||||
struct evbuffer *evb = evhttp_request_get_output_buffer(req); | struct evbuffer *evb = evhttp_request_get_output_buffer(req); | ||||
assert(evb); | assert(evb); | ||||
evbuffer_add(evb, strReply.data(), strReply.size()); | evbuffer_add(evb, strReply.data(), strReply.size()); | ||||
auto req_copy = req; | auto req_copy = req; | ||||
HTTPEvent *ev = new HTTPEvent(eventBase, true, [req_copy, nStatus] { | HTTPEvent *ev = new HTTPEvent(eventBase, true, [req_copy, nStatus] { | ||||
evhttp_send_reply(req_copy, nStatus, nullptr, nullptr); | evhttp_send_reply(req_copy, nStatus, nullptr, nullptr); | ||||
// Re-enable reading from the socket. This is the second part of the | // Re-enable reading from the socket. This is the second part of the | ||||
▲ Show 20 Lines • Show All 74 Lines • Show Last 20 Lines |