Changeset View
Changeset View
Standalone View
Standalone View
src/httpserver.cpp
Show First 20 Lines • Show All 270 Lines • ▼ Show 20 Lines | static void http_request_cb(struct evhttp_request *req, void *arg) { | ||||
if (i != iend) { | if (i != iend) { | ||||
std::unique_ptr<HTTPWorkItem> item( | std::unique_ptr<HTTPWorkItem> item( | ||||
new HTTPWorkItem(config, std::move(hreq), path, i->handler)); | new HTTPWorkItem(config, std::move(hreq), path, i->handler)); | ||||
assert(workQueue); | assert(workQueue); | ||||
if (workQueue->Enqueue(item.get())) { | if (workQueue->Enqueue(item.get())) { | ||||
/* if true, queue took ownership */ | /* if true, queue took ownership */ | ||||
item.release(); | item.release(); | ||||
} else { | } else { | ||||
LogPrintf("WARNING: request rejected because http work queue depth " | LogPrint(BCLog::HTTP, | ||||
"WARNING: request rejected because http work queue depth " | |||||
"exceeded, it can be increased with the -rpcworkqueue= " | "exceeded, it can be increased with the -rpcworkqueue= " | ||||
"setting\n"); | "setting\n"); | ||||
item->req->WriteReply(HTTP_INTERNAL, "Work queue depth exceeded"); | item->req->WriteReply(HTTP_INTERNAL, "Work queue depth exceeded"); | ||||
} | } | ||||
} else { | } else { | ||||
hreq->WriteReply(HTTP_NOTFOUND); | hreq->WriteReply(HTTP_NOTFOUND); | ||||
} | } | ||||
} | } | ||||
/** Callback to reject HTTP requests after shutdown. */ | /** Callback to reject HTTP requests after shutdown. */ | ||||
Show All 18 Lines | static bool HTTPBindAddresses(struct evhttp *http) { | ||||
std::vector<std::pair<std::string, uint16_t>> endpoints; | std::vector<std::pair<std::string, uint16_t>> endpoints; | ||||
// Determine what addresses to bind to | // Determine what addresses to bind to | ||||
if (!gArgs.IsArgSet("-rpcallowip")) { | if (!gArgs.IsArgSet("-rpcallowip")) { | ||||
// Default to loopback if not allowing external IPs. | // Default to loopback if not allowing external IPs. | ||||
endpoints.push_back(std::make_pair("::1", defaultPort)); | endpoints.push_back(std::make_pair("::1", defaultPort)); | ||||
endpoints.push_back(std::make_pair("127.0.0.1", defaultPort)); | endpoints.push_back(std::make_pair("127.0.0.1", defaultPort)); | ||||
if (gArgs.IsArgSet("-rpcbind")) { | if (gArgs.IsArgSet("-rpcbind")) { | ||||
LogPrintf("WARNING: option -rpcbind was ignored because " | LogPrint(BCLog::HTTP, | ||||
"WARNING: option -rpcbind was ignored because " | |||||
"-rpcallowip was not specified, refusing to allow " | "-rpcallowip was not specified, refusing to allow " | ||||
"everyone to connect\n"); | "everyone to connect\n"); | ||||
} | } | ||||
} else if (gArgs.IsArgSet("-rpcbind")) { | } else if (gArgs.IsArgSet("-rpcbind")) { | ||||
// Specific bind address. | // Specific bind address. | ||||
for (const std::string &strRPCBind : gArgs.GetArgs("-rpcbind")) { | for (const std::string &strRPCBind : gArgs.GetArgs("-rpcbind")) { | ||||
int port = defaultPort; | int port = defaultPort; | ||||
std::string host; | std::string host; | ||||
SplitHostPort(strRPCBind, port, host); | SplitHostPort(strRPCBind, port, host); | ||||
endpoints.push_back(std::make_pair(host, port)); | endpoints.push_back(std::make_pair(host, port)); | ||||
Show All 10 Lines | for (std::vector<std::pair<std::string, uint16_t>>::iterator i = | ||||
i != endpoints.end(); ++i) { | i != endpoints.end(); ++i) { | ||||
LogPrint(BCLog::HTTP, "Binding RPC on address %s port %i\n", i->first, | LogPrint(BCLog::HTTP, "Binding RPC on address %s port %i\n", i->first, | ||||
i->second); | i->second); | ||||
evhttp_bound_socket *bind_handle = evhttp_bind_socket_with_handle( | evhttp_bound_socket *bind_handle = evhttp_bind_socket_with_handle( | ||||
http, i->first.empty() ? nullptr : i->first.c_str(), i->second); | http, i->first.empty() ? nullptr : i->first.c_str(), i->second); | ||||
if (bind_handle) { | if (bind_handle) { | ||||
boundSockets.push_back(bind_handle); | boundSockets.push_back(bind_handle); | ||||
} else { | } else { | ||||
LogPrintf("Binding RPC on address %s port %i failed.\n", i->first, | LogPrint(BCLog::HTTP, "Binding RPC on address %s port %i failed.\n", | ||||
i->second); | i->first, i->second); | ||||
} | } | ||||
} | } | ||||
return !boundSockets.empty(); | return !boundSockets.empty(); | ||||
} | } | ||||
/** Simple wrapper to set thread name and run work queue */ | /** Simple wrapper to set thread name and run work queue */ | ||||
static void HTTPWorkQueueRun(WorkQueue<HTTPClosure> *queue) { | static void HTTPWorkQueueRun(WorkQueue<HTTPClosure> *queue) { | ||||
RenameThread("bitcoin-httpworker"); | RenameThread("bitcoin-httpworker"); | ||||
queue->Run(); | queue->Run(); | ||||
} | } | ||||
/** libevent event log callback */ | /** libevent event log callback */ | ||||
static void libevent_log_cb(int severity, const char *msg) { | static void libevent_log_cb(int severity, const char *msg) { | ||||
#ifndef EVENT_LOG_WARN | #ifndef EVENT_LOG_WARN | ||||
// EVENT_LOG_WARN was added in 2.0.19; but before then _EVENT_LOG_WARN existed. | // EVENT_LOG_WARN was added in 2.0.19; but before then _EVENT_LOG_WARN existed. | ||||
#define EVENT_LOG_WARN _EVENT_LOG_WARN | #define EVENT_LOG_WARN _EVENT_LOG_WARN | ||||
#endif | #endif | ||||
// Log warn messages and higher without debug category. | // Log warn messages and higher without debug category. | ||||
if (severity >= EVENT_LOG_WARN) { | if (severity >= EVENT_LOG_WARN) { | ||||
LogPrintf("libevent: %s\n", msg); | LogPrint(BCLog::HTTP, "libevent: %s\n", msg); | ||||
} else { | } else { | ||||
LogPrint(BCLog::LIBEVENT, "libevent: %s\n", msg); | LogPrint(BCLog::LIBEVENT, "libevent: %s\n", msg); | ||||
} | } | ||||
} | } | ||||
bool InitHTTPServer(Config &config) { | bool InitHTTPServer(Config &config) { | ||||
struct evhttp *http = 0; | struct evhttp *http = 0; | ||||
struct event_base *base = 0; | struct event_base *base = 0; | ||||
Show All 22 Lines | #ifdef WIN32 | ||||
evthread_use_windows_threads(); | evthread_use_windows_threads(); | ||||
#else | #else | ||||
evthread_use_pthreads(); | evthread_use_pthreads(); | ||||
#endif | #endif | ||||
// XXX RAII: Create a new event_base for Libevent use | // XXX RAII: Create a new event_base for Libevent use | ||||
base = event_base_new(); | base = event_base_new(); | ||||
if (!base) { | if (!base) { | ||||
LogPrintf("Couldn't create an event_base: exiting\n"); | LogPrint(BCLog::HTTP, "Couldn't create an event_base: exiting\n"); | ||||
return false; | return false; | ||||
} | } | ||||
// XXX RAII: Create a new evhttp object to handle requests | // XXX RAII: Create a new evhttp object to handle requests | ||||
http = evhttp_new(base); | http = evhttp_new(base); | ||||
if (!http) { | if (!http) { | ||||
LogPrintf("couldn't create evhttp. Exiting.\n"); | LogPrint(BCLog::HTTP, "couldn't create evhttp. Exiting.\n"); | ||||
event_base_free(base); | event_base_free(base); | ||||
return false; | return false; | ||||
} | } | ||||
evhttp_set_timeout( | evhttp_set_timeout( | ||||
http, gArgs.GetArg("-rpcservertimeout", DEFAULT_HTTP_SERVER_TIMEOUT)); | http, gArgs.GetArg("-rpcservertimeout", DEFAULT_HTTP_SERVER_TIMEOUT)); | ||||
evhttp_set_max_headers_size(http, MAX_HEADERS_SIZE); | evhttp_set_max_headers_size(http, MAX_HEADERS_SIZE); | ||||
evhttp_set_max_body_size( | evhttp_set_max_body_size( | ||||
http, MIN_SUPPORTED_BODY_SIZE + 2 * config.GetMaxBlockSize()); | http, MIN_SUPPORTED_BODY_SIZE + 2 * config.GetMaxBlockSize()); | ||||
evhttp_set_gencb(http, http_request_cb, &config); | evhttp_set_gencb(http, http_request_cb, &config); | ||||
// Only POST and OPTIONS are supported, but we return HTTP 405 for the | // Only POST and OPTIONS are supported, but we return HTTP 405 for the | ||||
// others | // others | ||||
evhttp_set_allowed_methods(http, | evhttp_set_allowed_methods(http, | ||||
EVHTTP_REQ_GET | EVHTTP_REQ_POST | | EVHTTP_REQ_GET | EVHTTP_REQ_POST | | ||||
EVHTTP_REQ_HEAD | EVHTTP_REQ_PUT | | EVHTTP_REQ_HEAD | EVHTTP_REQ_PUT | | ||||
EVHTTP_REQ_DELETE | EVHTTP_REQ_OPTIONS); | EVHTTP_REQ_DELETE | EVHTTP_REQ_OPTIONS); | ||||
if (!HTTPBindAddresses(http)) { | if (!HTTPBindAddresses(http)) { | ||||
LogPrintf("Unable to bind any endpoint for RPC server\n"); | LogPrint(BCLog::HTTP, "Unable to bind any endpoint for RPC server\n"); | ||||
evhttp_free(http); | evhttp_free(http); | ||||
event_base_free(base); | event_base_free(base); | ||||
return false; | return false; | ||||
} | } | ||||
LogPrint(BCLog::HTTP, "Initialized HTTP server\n"); | LogPrint(BCLog::HTTP, "Initialized HTTP server\n"); | ||||
int workQueueDepth = std::max( | int workQueueDepth = std::max( | ||||
(long)gArgs.GetArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L); | (long)gArgs.GetArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L); | ||||
LogPrintf("HTTP: creating work queue of depth %d\n", workQueueDepth); | LogPrint(BCLog::HTTP, "HTTP: creating work queue of depth %d\n", | ||||
workQueueDepth); | |||||
workQueue = new WorkQueue<HTTPClosure>(workQueueDepth); | workQueue = new WorkQueue<HTTPClosure>(workQueueDepth); | ||||
eventBase = base; | eventBase = base; | ||||
eventHTTP = http; | eventHTTP = http; | ||||
return true; | return true; | ||||
} | } | ||||
std::thread threadHTTP; | std::thread threadHTTP; | ||||
std::future<bool> threadResult; | std::future<bool> threadResult; | ||||
bool StartHTTPServer() { | bool 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); | LogPrint(BCLog::HTTP, "HTTP: starting %d worker threads\n", rpcThreads); | ||||
std::packaged_task<bool(event_base *, evhttp *)> task(ThreadHTTP); | std::packaged_task<bool(event_base *, evhttp *)> task(ThreadHTTP); | ||||
threadResult = task.get_future(); | threadResult = task.get_future(); | ||||
threadHTTP = std::thread(std::move(task), eventBase, eventHTTP); | threadHTTP = std::thread(std::move(task), eventBase, eventHTTP); | ||||
for (int i = 0; i < rpcThreads; i++) { | for (int i = 0; i < rpcThreads; i++) { | ||||
std::thread rpc_worker(HTTPWorkQueueRun, workQueue); | std::thread rpc_worker(HTTPWorkQueueRun, workQueue); | ||||
rpc_worker.detach(); | rpc_worker.detach(); | ||||
} | } | ||||
Show All 27 Lines | if (eventBase) { | ||||
// event_base_loopexit, but that didn't work as expected in at least | // 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 | // 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 | // that appears to be solved, so in the future that solution could be | ||||
// used again (if desirable). | // used again (if desirable). | ||||
// (see discussion in https://github.com/bitcoin/bitcoin/pull/6990) | // (see discussion in https://github.com/bitcoin/bitcoin/pull/6990) | ||||
if (threadResult.valid() && | if (threadResult.valid() && | ||||
threadResult.wait_for(std::chrono::milliseconds(2000)) == | threadResult.wait_for(std::chrono::milliseconds(2000)) == | ||||
std::future_status::timeout) { | std::future_status::timeout) { | ||||
LogPrintf("HTTP event loop did not exit within allotted time, " | LogPrint(BCLog::HTTP, | ||||
"HTTP event loop did not exit within allotted time, " | |||||
"sending loopbreak\n"); | "sending loopbreak\n"); | ||||
event_base_loopbreak(eventBase); | event_base_loopbreak(eventBase); | ||||
} | } | ||||
threadHTTP.join(); | threadHTTP.join(); | ||||
} | } | ||||
if (eventHTTP) { | if (eventHTTP) { | ||||
evhttp_free(eventHTTP); | evhttp_free(eventHTTP); | ||||
eventHTTP = 0; | eventHTTP = 0; | ||||
} | } | ||||
Show All 33 Lines | if (tv == nullptr) { | ||||
evtimer_add(ev, tv); | evtimer_add(ev, tv); | ||||
} | } | ||||
} | } | ||||
HTTPRequest::HTTPRequest(struct evhttp_request *_req) | HTTPRequest::HTTPRequest(struct evhttp_request *_req) | ||||
: req(_req), replySent(false) {} | : req(_req), replySent(false) {} | ||||
HTTPRequest::~HTTPRequest() { | HTTPRequest::~HTTPRequest() { | ||||
if (!replySent) { | if (!replySent) { | ||||
// Keep track of whether reply was sent to avoid request leaks | // Keep track of whether reply was sent to avoid request leaks | ||||
LogPrintf("%s: Unhandled request\n", __func__); | LogPrint(BCLog::HTTP, "%s: Unhandled request\n", __func__); | ||||
WriteReply(HTTP_INTERNAL, "Unhandled request"); | WriteReply(HTTP_INTERNAL, "Unhandled request"); | ||||
} | } | ||||
// evhttpd cleans up the request, as long as a reply was sent. | // evhttpd cleans up the request, as long as a reply was sent. | ||||
} | } | ||||
std::pair<bool, std::string> HTTPRequest::GetHeader(const std::string &hdr) { | std::pair<bool, std::string> HTTPRequest::GetHeader(const std::string &hdr) { | ||||
const struct evkeyvalq *headers = evhttp_request_get_input_headers(req); | const struct evkeyvalq *headers = evhttp_request_get_input_headers(req); | ||||
assert(headers); | assert(headers); | ||||
▲ Show 20 Lines • Show All 110 Lines • Show Last 20 Lines |