Branch data Line data Source code
1 : : // Copyright (c) 2015-present The Bitcoin Core developers
2 : : // Distributed under the MIT software license, see the accompanying
3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 : :
5 : : #include <bitcoin-build-config.h> // IWYU pragma: keep
6 : :
7 : : #include <httpserver.h>
8 : :
9 : : #include <chainparamsbase.h>
10 : : #include <common/args.h>
11 : : #include <common/messages.h>
12 : : #include <common/url.h>
13 : : #include <compat/compat.h>
14 : : #include <logging.h>
15 : : #include <netbase.h>
16 : : #include <node/interface_ui.h>
17 : : #include <rpc/protocol.h>
18 : : #include <span.h>
19 : : #include <sync.h>
20 : : #include <util/check.h>
21 : : #include <util/signalinterrupt.h>
22 : : #include <util/sock.h>
23 : : #include <util/strencodings.h>
24 : : #include <util/thread.h>
25 : : #include <util/threadnames.h>
26 : : #include <util/threadpool.h>
27 : : #include <util/time.h>
28 : : #include <util/translation.h>
29 : :
30 : : #include <condition_variable>
31 : : #include <cstdio>
32 : : #include <cstdlib>
33 : : #include <memory>
34 : : #include <optional>
35 : : #include <span>
36 : : #include <string>
37 : : #include <string_view>
38 : : #include <thread>
39 : : #include <unordered_map>
40 : : #include <vector>
41 : :
42 : : #include <sys/types.h>
43 : : #include <sys/stat.h>
44 : :
45 : : //! The set of sockets cannot be modified while waiting, so
46 : : //! the sleep time needs to be small to avoid new sockets stalling.
47 : : static constexpr auto SELECT_TIMEOUT{50ms};
48 : :
49 : : //! Explicit alias for setting socket option methods.
50 : : static constexpr int SOCKET_OPTION_TRUE{1};
51 : :
52 : : using common::InvalidPortErrMsg;
53 : : using http_bitcoin::HTTPRequest;
54 : :
55 : 3559 : struct HTTPPathHandler
56 : : {
57 : 2368 : HTTPPathHandler(std::string _prefix, bool _exactMatch, HTTPRequestHandler _handler):
58 [ - + + - ]: 4736 : prefix(_prefix), exactMatch(_exactMatch), handler(_handler)
59 : : {
60 : 2368 : }
61 : : std::string prefix;
62 : : bool exactMatch;
63 : : HTTPRequestHandler handler;
64 : : };
65 : :
66 : : /** HTTP module state */
67 : :
68 : : static std::unique_ptr<http_bitcoin::HTTPServer> g_http_server{nullptr};
69 : : //! Handlers for (sub)paths
70 : : static GlobalMutex g_httppathhandlers_mutex;
71 : : static std::vector<HTTPPathHandler> pathHandlers GUARDED_BY(g_httppathhandlers_mutex);
72 : : /// \anchor http_pool
73 : : //! Http thread pool - future: encapsulate in HttpContext
74 : : static ThreadPool g_threadpool_http("http");
75 : : static int g_max_queue_depth{100};
76 : :
77 : : namespace http_bitcoin {
78 : : /** Check if a network address is allowed to access the HTTP server */
79 : 3117 : bool HTTPServer::ClientAllowed(const CNetAddr& netaddr) const
80 : : {
81 [ + - ]: 3117 : if (!netaddr.IsValid())
82 : : return false;
83 [ + + ]: 3131 : for(const CSubNet& subnet : m_allow_subnets)
84 [ + + ]: 3129 : if (subnet.Match(netaddr))
85 : : return true;
86 : : return false;
87 : : }
88 : :
89 : : /** Initialize ACL list for HTTP server */
90 : 1192 : bool HTTPServer::InitHTTPAllowList()
91 : : {
92 : : // Must be run before StartSocketThreads() because ThreadSocketHandler()
93 : : // will check m_allow_subnets from the I/O thread.
94 : 1192 : Assume(!m_thread_socket_handler.joinable());
95 : :
96 : 1192 : m_allow_subnets.clear();
97 [ + - + - : 3576 : m_allow_subnets.emplace_back(LookupHost("127.0.0.1", false).value(), 8); // always allow IPv4 local subnet
+ - ]
98 [ + - + - : 3576 : m_allow_subnets.emplace_back(LookupHost("::1", false).value()); // always allow IPv6 localhost
+ - ]
99 [ + - + + ]: 1202 : for (const std::string& strAllow : gArgs.GetArgs("-rpcallowip")) {
100 [ + - ]: 10 : const CSubNet subnet{LookupSubNet(strAllow)};
101 [ + - - + ]: 10 : if (!subnet.IsValid()) {
102 [ # # ]: 0 : uiInterface.ThreadSafeMessageBox(
103 [ # # ]: 0 : Untranslated(strprintf("Invalid -rpcallowip subnet specification: %s. Valid values are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0), a network/CIDR (e.g. 1.2.3.4/24), all ipv4 (0.0.0.0/0), or all ipv6 (::/0). RFC4193 is allowed only if -cjdnsreachable=0.", strAllow)),
104 [ # # ]: 0 : CClientUIInterface::MSG_ERROR);
105 : 0 : return false;
106 : : }
107 [ + - ]: 10 : m_allow_subnets.push_back(subnet);
108 : 1202 : }
109 : 1192 : std::string strAllowed;
110 [ + + ]: 3586 : for (const CSubNet& subnet : m_allow_subnets)
111 [ + - - + ]: 7182 : strAllowed += subnet.ToString() + " ";
112 [ + - + + : 1192 : LogDebug(BCLog::HTTP, "Allowing HTTP connections from: %s\n", strAllowed);
+ - ]
113 : 1192 : return true;
114 : 1192 : }
115 : : } // namespace http_bitcoin
116 : :
117 : : /** HTTP request method as string - use for logging only */
118 : 218253 : std::string_view RequestMethodString(HTTPRequestMethod m)
119 : : {
120 [ + + - - : 218253 : switch (m) {
+ - ]
121 : 800 : using enum HTTPRequestMethod;
122 : 800 : case GET: return "GET";
123 : 217448 : case POST: return "POST";
124 : 0 : case HEAD: return "HEAD";
125 : 0 : case PUT: return "PUT";
126 : 5 : case UNKNOWN: return "unknown";
127 : : } // no default case, so the compiler can warn about missing cases
128 : 0 : assert(false);
129 : : }
130 : :
131 : 594 : static void WriteNoStoreErrorReply(HTTPRequest& req, HTTPStatusCode status, std::string_view reply = {})
132 : : {
133 [ + - + - ]: 1188 : req.WriteHeader("Cache-Control", "no-store");
134 : 594 : req.WriteReply(status, reply);
135 : 594 : }
136 : :
137 : 218261 : static void MaybeDispatchRequestToWorker(std::shared_ptr<HTTPRequest> hreq)
138 : : {
139 : : // Early reject unknown HTTP methods
140 [ + + ]: 218261 : if (hreq->GetRequestMethod() == HTTPRequestMethod::UNKNOWN) {
141 [ + - + - : 5 : LogDebug(BCLog::HTTP, "HTTP request from %s rejected: Unknown HTTP request method\n",
+ - ]
142 : : hreq->GetPeer().ToStringAddrPort());
143 : 5 : WriteNoStoreErrorReply(*hreq, HTTP_BAD_METHOD);
144 : 5 : return;
145 : : }
146 : :
147 : : // Find registered handler for prefix
148 [ - + ]: 218256 : std::string strURI = hreq->GetURI();
149 [ + - ]: 218256 : std::string path;
150 [ + - ]: 218256 : LOCK(g_httppathhandlers_mutex);
151 : 218256 : std::vector<HTTPPathHandler>::const_iterator i = pathHandlers.begin();
152 : 218256 : std::vector<HTTPPathHandler>::const_iterator iend = pathHandlers.end();
153 [ + + ]: 245840 : for (; i != iend; ++i) {
154 : 245828 : bool match = false;
155 [ + + ]: 245828 : if (i->exactMatch)
156 : 218256 : match = (strURI == i->prefix);
157 : : else
158 [ - + - + ]: 27572 : match = strURI.starts_with(i->prefix);
159 [ + + ]: 245828 : if (match) {
160 [ - + + - ]: 218244 : path = strURI.substr(i->prefix.size());
161 : 218244 : break;
162 : : }
163 : : }
164 : :
165 : : // Dispatch to worker thread
166 [ + + ]: 218256 : if (i != iend) {
167 [ + - + + ]: 218244 : if (static_cast<int>(g_threadpool_http.WorkQueueSize()) >= g_max_queue_depth) {
168 [ + - ]: 564 : LogWarning("Request rejected because http work queue depth exceeded, it can be increased with the -rpcworkqueue= setting");
169 [ + - ]: 564 : WriteNoStoreErrorReply(*hreq, HTTP_SERVICE_UNAVAILABLE, "Work queue depth exceeded");
170 : : return;
171 : : }
172 : :
173 [ + - ]: 217680 : auto item = [req = hreq, in_path = std::move(path), fn = i->handler]() {
174 [ + - ]: 217680 : std::string err_msg;
175 : 217680 : try {
176 [ + - ]: 217680 : fn(req.get(), in_path);
177 : 217680 : return;
178 [ - - ]: 0 : } catch (const std::exception& e) {
179 [ - - - - ]: 0 : LogWarning("Unexpected error while processing request for '%s'. Error msg: '%s'", req->GetURI(), e.what());
180 [ - - ]: 0 : err_msg = e.what();
181 : 0 : } catch (...) {
182 [ - - - - ]: 0 : LogWarning("Unknown error while processing request for '%s'", req->GetURI());
183 [ - - ]: 0 : err_msg = "unknown error";
184 [ - - ]: 0 : }
185 : : // Reply so the client doesn't hang waiting for the response.
186 [ - - - - : 0 : req->WriteHeader("Connection", "close");
- - ]
187 : : // TODO: Implement specific error formatting for the REST and JSON-RPC servers responses.
188 [ - - - - ]: 0 : WriteNoStoreErrorReply(*req, HTTP_INTERNAL_SERVER_ERROR, err_msg);
189 [ + - ]: 217680 : };
190 : :
191 [ - + ]: 217680 : if (auto res = g_threadpool_http.Submit(std::move(item)); !res.has_value()) {
192 : 0 : Assume(hreq.use_count() == 1); // ensure request will be deleted
193 : : // Both SubmitError::Inactive and SubmitError::Interrupted mean shutdown
194 [ # # ]: 0 : LogWarning("HTTP request rejected during server shutdown: '%s'", SubmitErrorString(res.error()));
195 [ # # ]: 0 : WriteNoStoreErrorReply(*hreq, HTTP_SERVICE_UNAVAILABLE, "Request rejected during server shutdown");
196 : 0 : return;
197 : 0 : }
198 : 217680 : } else {
199 [ + - ]: 12 : WriteNoStoreErrorReply(*hreq, HTTP_NOT_FOUND);
200 : : }
201 : 218256 : }
202 : :
203 : 0 : static void RejectRequest(std::unique_ptr<http_bitcoin::HTTPRequest> hreq)
204 : : {
205 [ # # ]: 0 : LogDebug(BCLog::HTTP, "Rejecting request while shutting down");
206 : 0 : WriteNoStoreErrorReply(*hreq, HTTP_SERVICE_UNAVAILABLE);
207 : 0 : }
208 : :
209 : 1189 : static std::vector<std::pair<std::string, uint16_t>> GetBindAddresses()
210 : : {
211 : 2378 : uint16_t http_port{static_cast<uint16_t>(gArgs.GetIntArg("-rpcport", BaseParams().RPCPort()))};
212 : 1189 : std::vector<std::pair<std::string, uint16_t>> endpoints;
213 : :
214 : : // Determine what addresses to bind to
215 : : // To prevent misconfiguration and accidental exposure of the RPC
216 : : // interface, require -rpcallowip and -rpcbind to both be specified
217 : : // together. If either is missing, ignore both values, bind to localhost
218 : : // instead, and log warnings.
219 [ + - + - : 2392 : if (gArgs.GetArgs("-rpcallowip").empty() || gArgs.GetArgs("-rpcbind").empty()) { // Default to loopback if not allowing external IPs
+ + + - +
- + - + +
+ + - - -
- - - ]
220 [ + - ]: 1182 : endpoints.emplace_back("::1", http_port);
221 [ + - ]: 1182 : endpoints.emplace_back("127.0.0.1", http_port);
222 [ + - + - : 1182 : if (!gArgs.GetArgs("-rpcallowip").empty()) {
- + ]
223 [ # # ]: 0 : LogWarning("Option -rpcallowip was specified without -rpcbind; this doesn't usually make sense");
224 : : }
225 [ + - + - : 1182 : if (!gArgs.GetArgs("-rpcbind").empty()) {
- + ]
226 [ # # ]: 0 : LogWarning("Option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect");
227 : : }
228 : : } else { // Specific bind addresses
229 [ + - + - : 25 : for (const std::string& strRPCBind : gArgs.GetArgs("-rpcbind")) {
+ + ]
230 : 11 : uint16_t port{http_port};
231 [ - + ]: 11 : std::string host;
232 [ - + + - : 11 : if (!SplitHostPort(strRPCBind, port, host)) {
- + ]
233 [ # # # # : 0 : LogError("%s\n", InvalidPortErrMsg("-rpcbind", strRPCBind).original);
# # ]
234 : 0 : return {}; // empty
235 : : }
236 [ + - ]: 11 : endpoints.emplace_back(host, port);
237 : 18 : }
238 : : }
239 : 1189 : return endpoints;
240 : 1189 : }
241 : :
242 : 2368 : void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler)
243 : : {
244 [ + + ]: 2368 : LogDebug(BCLog::HTTP, "Registering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch);
245 : 2368 : LOCK(g_httppathhandlers_mutex);
246 [ + - ]: 2368 : pathHandlers.emplace_back(prefix, exactMatch, handler);
247 : 2368 : }
248 : :
249 : 19696 : void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch)
250 : : {
251 : 19696 : LOCK(g_httppathhandlers_mutex);
252 : 19696 : std::vector<HTTPPathHandler>::iterator i = pathHandlers.begin();
253 : 19696 : std::vector<HTTPPathHandler>::iterator iend = pathHandlers.end();
254 [ + + ]: 19696 : for (; i != iend; ++i)
255 [ + - - + ]: 2368 : if (i->prefix == prefix && i->exactMatch == exactMatch)
256 : : break;
257 [ + + ]: 19696 : if (i != iend)
258 : : {
259 [ + - + + : 2368 : LogDebug(BCLog::HTTP, "Unregistering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch);
+ - ]
260 : 2368 : pathHandlers.erase(i);
261 : : }
262 : 19696 : }
263 : :
264 : : namespace http_bitcoin {
265 : : using util::Split;
266 : :
267 : 1046498 : std::optional<std::string> HTTPHeaders::FindFirst(const std::string_view key) const
268 : : {
269 [ + + ]: 5569898 : for (const auto& item : m_headers) {
270 [ - + + + ]: 4959495 : if (CaseInsensitiveEqual(key, item.first)) {
271 [ - + ]: 872190 : return item.second;
272 : : }
273 : : }
274 : 610403 : return std::nullopt;
275 : : }
276 : :
277 : 392493 : std::vector<std::string_view> HTTPHeaders::FindAll(const std::string_view key) const
278 : : {
279 : 392493 : std::vector<std::string_view> ret;
280 [ + + ]: 2742114 : for (const auto& item : m_headers) {
281 [ - + + - : 2349621 : if (CaseInsensitiveEqual(key, item.first)) {
+ + ]
282 [ - + + - ]: 2349621 : ret.push_back(item.second);
283 : : }
284 : : }
285 : 392493 : return ret;
286 : 0 : }
287 : :
288 : 1965823 : void HTTPHeaders::Write(std::string&& key, std::string&& value)
289 : : {
290 : 1965823 : m_headers.emplace_back(std::move(key), std::move(value));
291 : 1965823 : }
292 : :
293 : 1015 : void HTTPHeaders::RemoveAll(std::string_view key)
294 : : {
295 : 1015 : auto moved = std::ranges::remove_if(m_headers, [key] (auto& pair) {
296 [ - + ]: 3051 : return CaseInsensitiveEqual(key, pair.first);
297 : : });
298 : 1015 : m_headers.erase(moved.begin(), moved.end());
299 : 1015 : }
300 : :
301 : 218325 : bool HTTPHeaders::Read(util::LineReader& reader, bool write)
302 : : {
303 : : // Headers https://httpwg.org/specs/rfc9110.html#rfc.section.6.3
304 : : // A sequence of Field Lines https://httpwg.org/specs/rfc9110.html#rfc.section.5.2
305 : 218325 : size_t start{reader.Consumed()};
306 [ + + ]: 1526895 : while (auto maybe_line = reader.ReadLine()) {
307 [ + + + - ]: 1526882 : if (reader.Consumed() - start + m_consumed > MAX_HEADERS_SIZE) throw std::runtime_error("HTTP headers exceed size limit");
308 : :
309 [ + + ]: 1526878 : const std::string_view& line = *maybe_line;
310 : :
311 : : // An empty line indicates end of the headers section https://www.rfc-editor.org/rfc/rfc2616#section-4
312 [ + + ]: 1526878 : if (line.empty()) {
313 : : // Ensure all headers are accounted for in case there is a chunked trailer
314 : 218298 : m_consumed += reader.Consumed() - start;
315 : 218298 : return true;
316 : : }
317 : :
318 : : // "Field values containing CR, LF, or NUL characters are invalid and dangerous"
319 : : // https://httpwg.org/specs/rfc9110.html#rfc.section.5.5
320 : : // A sender MUST NOT generate a bare CR (a CR character not immediately followed by LF)
321 : : // within any protocol elements other than the content.
322 : : // A recipient of such a bare CR MUST consider that element to be invalid...
323 : : // https://httpwg.org/specs/rfc9112.html#rfc.section.2.2
324 [ + + + - ]: 1308580 : if (line.find_first_of("\r\n\0", 0, 3) != std::string_view::npos) throw std::runtime_error("Header contains invalid character");
325 : :
326 : : // Header line must have at least one ":"
327 : : // keys are not allowed to have delimiters like ":" but values are
328 : : // https://httpwg.org/specs/rfc9110.html#rfc.section.5.6.2
329 : 1308577 : const size_t pos{line.find(':')};
330 [ + + + - ]: 1308577 : if (pos == std::string_view::npos) throw std::runtime_error("HTTP header missing colon (:)");
331 : :
332 : : // Whitespace is strictly not allowed in the field-name (key)
333 : : // https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.2
334 : 1308573 : std::string_view key = line.substr(0, pos);
335 [ + + + - ]: 1308573 : if (key.find_first_of(" \t\n\r\f\v") != std::string_view::npos) throw std::runtime_error("Invalid header field-name contains whitespace");
336 : : // Whitespace is optional in the value and can be trimmed
337 : 1308571 : std::string value = util::TrimString(std::string_view(line).substr(pos + 1));
338 : :
339 : : // Header keys are Field Names: https://httpwg.org/specs/rfc9110.html#fields.names
340 : : // which consist of "tokens": https://httpwg.org/specs/rfc9110.html#rfc.section.5.6.2
341 : : // that can not be empty.
342 [ + + + - ]: 1308571 : if (key.empty()) throw std::runtime_error("Empty HTTP header name");
343 : :
344 [ + + ]: 1308570 : if (write) {
345 [ + - + - ]: 2617134 : Write(std::string(key), std::move(value));
346 : : }
347 : 1308570 : }
348 : :
349 : : // We have not received all the request headers yet.
350 : : // Keep track of how much data we have already consumed to enforce
351 : : // the total limit over multiple read operations.
352 : 13 : m_consumed += reader.Consumed() - start;
353 : :
354 : 13 : return false;
355 : : }
356 : :
357 : 218280 : std::string HTTPHeaders::Stringify() const
358 : : {
359 : 218280 : std::string out;
360 [ + - + + ]: 875535 : for (const auto& [key, value] : m_headers) {
361 [ + - + - : 1971765 : out += key + ": " + value + "\r\n";
- + ]
362 : : }
363 : :
364 : : // Headers are terminated by an empty line
365 [ + - ]: 218280 : out += "\r\n";
366 : :
367 : 218280 : return out;
368 : 0 : }
369 : :
370 : 218279 : std::string HTTPResponse::StringifyHeaders() const
371 : : {
372 : 218279 : return strprintf("HTTP/%d.%d %d %s\r\n%s",
373 : 218279 : m_version.major,
374 : 218279 : m_version.minor,
375 : 218279 : m_status,
376 : 218279 : HTTPStatusReasonString(m_status),
377 [ + - ]: 436558 : m_headers.Stringify());
378 : : }
379 : :
380 : 218317 : bool HTTPRequest::LoadControlData(LineReader& reader)
381 : : {
382 : 218317 : auto maybe_line = reader.ReadLine();
383 [ + - ]: 218315 : if (!maybe_line) return false;
384 [ + + ]: 218315 : const std::string_view& request_line = *maybe_line;
385 : :
386 : : // Request Line aka Control Data https://httpwg.org/specs/rfc9110.html#rfc.section.6.2
387 : : // Three words separated by spaces, terminated by \n or \r\n
388 [ + + + - ]: 218315 : if (request_line.length() < MIN_REQUEST_LINE_LENGTH) throw std::runtime_error("HTTP request line too short");
389 : :
390 : : // NUL is not a valid tchar and would silently truncate
391 : : // C-string-based parsers rather than being rejected as malformed.
392 : : // tchar: https://www.rfc-editor.org/info/rfc7230/#section-3.2.6
393 [ + + + - ]: 218311 : if (request_line.find('\0') != std::string_view::npos) throw std::runtime_error("Invalid request line contains NUL");
394 : :
395 : 218309 : const std::vector<std::string_view> parts{Split<std::string_view>(request_line, " ")};
396 [ - + + + : 218309 : if (parts.size() != 3) throw std::runtime_error("HTTP request line malformed");
+ - ]
397 : :
398 [ + + ]: 218308 : if (parts[0] == "GET") {
399 : 829 : m_method = HTTPRequestMethod::GET;
400 [ + + ]: 217479 : } else if (parts[0] == "POST") {
401 : 217474 : m_method = HTTPRequestMethod::POST;
402 [ - + ]: 5 : } else if (parts[0] == "HEAD") {
403 : 0 : m_method = HTTPRequestMethod::HEAD;
404 [ - + ]: 5 : } else if (parts[0] == "PUT") {
405 : 0 : m_method = HTTPRequestMethod::PUT;
406 : : } else {
407 : 5 : m_method = HTTPRequestMethod::UNKNOWN;
408 : : }
409 : :
410 [ + - ]: 218308 : m_target = parts[1];
411 : :
412 [ + + + - ]: 218308 : if (parts[2].rfind("HTTP/") != 0) throw std::runtime_error("HTTP request line malformed");
413 : :
414 : : // Version is exactly two decimal digits separated by a decimal point
415 : : // https://httpwg.org/specs/rfc9110.html#rfc.section.2.5
416 [ + - + - ]: 218307 : const std::vector<std::string_view> version_parts{Split<std::string_view>(parts[2].substr(5), ".")};
417 [ - + + + : 218307 : if (version_parts.size() != 2) throw std::runtime_error("HTTP request line malformed");
+ - ]
418 [ + + + + : 218306 : if (version_parts[0].size() != 1 || version_parts[1].size() != 1) throw std::runtime_error("HTTP bad version");
+ - ]
419 : 218304 : auto major = ToIntegral<uint8_t>(version_parts[0]);
420 : 218304 : auto minor = ToIntegral<uint8_t>(version_parts[1]);
421 [ + - + + : 218304 : if (!major || !minor || major != 1 || minor > 9) throw std::runtime_error("HTTP bad version");
+ + + - +
- ]
422 : 218300 : m_version.major = major.value();
423 : 218300 : m_version.minor = minor.value();
424 : :
425 : 218300 : return true;
426 : 218307 : }
427 : :
428 : 218305 : bool HTTPRequest::LoadHeaders(LineReader& reader)
429 : : {
430 : 218305 : return m_headers.Read(reader);
431 : : }
432 : :
433 : 393038 : bool HTTPRequest::LoadBody(LineReader& reader)
434 : : {
435 : : // https://httpwg.org/specs/rfc9112.html#message.body
436 : 393038 : auto transfer_encoding_header = m_headers.FindFirst("Transfer-Encoding");
437 [ + + - + : 393584 : if (transfer_encoding_header && ToLower(transfer_encoding_header.value()) == "chunked") {
+ - + - +
+ ]
438 : : // Transfer-Encoding: https://datatracker.ietf.org/doc/html/rfc7230.html#section-3.3.1
439 : : // Chunked Transfer Coding: https://datatracker.ietf.org/doc/html/rfc7230.html#section-4.1
440 : : // see evhttp_handle_chunked_read() in libevent http.c
441 [ + - + + ]: 1090 : while (reader.Remaining() > 0) {
442 [ + + ]: 561 : if (!m_chunk_size) {
443 [ + - ]: 33 : auto maybe_chunk_size = reader.ReadLine();
444 [ + - ]: 33 : if (!maybe_chunk_size) return false;
445 : :
446 : : // Allow (but ignore) Chunk Extensions
447 : : // See https://www.rfc-editor.org/rfc/rfc9112.html#name-chunk-extensions
448 [ + + ]: 33 : std::string_view chunk_size_noext{maybe_chunk_size.value()};
449 : 33 : const auto semicolon_pos = chunk_size_noext.find(';');
450 [ + + ]: 33 : if (semicolon_pos != chunk_size_noext.npos) {
451 : 3 : chunk_size_noext.remove_suffix(chunk_size_noext.size() - semicolon_pos);
452 : : }
453 : :
454 [ + - ]: 33 : m_chunk_size = ToIntegral<uint64_t>(util::TrimStringView(chunk_size_noext), /*base=*/16);
455 [ + + + - ]: 33 : if (!m_chunk_size) throw std::runtime_error("Cannot parse chunk length value");
456 : :
457 [ - + + - : 32 : if ((m_body.size() > MAX_BODY_SIZE) ||
+ + ]
458 [ + + ]: 32 : (*m_chunk_size > MAX_BODY_SIZE - m_body.size()))
459 [ + - ]: 3 : throw ContentTooLargeError("Chunk will exceed max body size");
460 : : }
461 : :
462 : : // We either just read the chunk size, or we have it saved
463 : : // from a prior I/O loop iteration
464 [ + + ]: 557 : Assume(m_chunk_size);
465 : :
466 : : // Last chunk has size 0
467 [ + + ]: 557 : if (*m_chunk_size == 0) {
468 : : // Validate Chunked Trailer section, which is used for
469 : : // additional headers sent at the end of the message.
470 : : // Data consumed here is counted towards MAX_HEADERS_SIZE
471 : : // along with the headers we read in the beginning of the request.
472 : : // At this time we ignore and drop these data after validating.
473 : : // See https://httpwg.org/specs/rfc9112.html#rfc.section.7.1.2
474 [ + + ]: 11 : return m_headers.Read(reader, /*write=*/false);
475 : : }
476 : :
477 : : // We have not read the entire chunk from the buffer yet
478 [ + + ]: 546 : if (m_chunk_read < *m_chunk_size) {
479 : : // Get what we can from the buffer
480 [ + - ]: 545 : const uint64_t chunk_need{*m_chunk_size - m_chunk_read};
481 [ + - + + ]: 545 : const uint64_t buffer_has{std::min(chunk_need, static_cast<uint64_t>(reader.Remaining()))};
482 : :
483 : : // Pack [partial] chunk onto body and update state
484 [ + - + - ]: 545 : m_body += reader.ReadLength(buffer_has);
485 : 545 : m_chunk_read += buffer_has;
486 : : }
487 : :
488 : : // Even though every chunk size is explicitly declared,
489 : : // they are still terminated by a CRLF we don't need,
490 : : // just consume it here.
491 [ + + ]: 546 : if (m_chunk_read == *m_chunk_size) {
492 [ + - ]: 23 : auto crlf = reader.ReadLine();
493 [ + + ]: 23 : if (!crlf) {
494 : : // CRLF not found before end of buffer: it has not been received by our socket yet.
495 : : return false;
496 : : }
497 : : // CRLF was found but there was unexpected data after the chunk_sized chunk
498 [ + + + - ]: 22 : if (!crlf.value().empty()) throw std::runtime_error("Improperly terminated chunk");
499 : :
500 : : // Clear state for next chunk
501 [ + - ]: 21 : m_chunk_size.reset();
502 : 21 : m_chunk_read = 0;
503 : : }
504 : : }
505 : :
506 : : // We read all the chunks but never got the last chunk, wait for client to send more
507 : : return false;
508 : : } else {
509 : : // No Content-length or Transfer-Encoding header means no body, see libevent evhttp_get_body()
510 [ + - ]: 392492 : auto content_length_values{m_headers.FindAll("Content-Length")};
511 [ + + ]: 392492 : if (content_length_values.empty()) return true;
512 : :
513 : : // Duplicate Content-Length headers are allowed only if they all have the same value
514 : : // https://www.rfc-editor.org/rfc/rfc7230#section-3.3.3
515 : 391693 : const auto& first_content_length_value{content_length_values[0]};
516 [ - + + + ]: 391694 : for (size_t i = 1; i < content_length_values.size(); ++i) {
517 [ + + + - ]: 3 : if (content_length_values[i] != first_content_length_value) throw std::runtime_error("Differing Content-Length values");
518 : : }
519 : :
520 : 391691 : const auto content_length{ToIntegral<uint64_t>(first_content_length_value)};
521 [ + + + - ]: 391691 : if (!content_length) throw std::runtime_error("Cannot parse Content-Length value");
522 : :
523 [ + + + - ]: 391689 : if (*content_length > MAX_BODY_SIZE) throw ContentTooLargeError("Max body size exceeded");
524 : :
525 : : // A large body may arrive over multiple I/O loop iterations. Copy
526 : : // whatever the buffer has now; m_body's size tracks our progress.
527 [ - + ]: 391687 : const uint64_t body_need{*content_length - m_body.size()};
528 [ + - + + ]: 391687 : const uint64_t buffer_has{std::min(body_need, static_cast<uint64_t>(reader.Remaining()))};
529 : :
530 : : // Pack [partial] body on and update state
531 [ + - + - ]: 391687 : m_body += reader.ReadLength(buffer_has);
532 : :
533 [ - + ]: 391687 : return m_body.size() == *content_length;
534 : 392492 : }
535 : 393026 : }
536 : :
537 : 218278 : void HTTPRequest::WriteReply(HTTPStatusCode status, std::span<const std::byte> reply_body)
538 : : {
539 : 218278 : HTTPResponse res;
540 : :
541 : : // Some response headers are determined in advance and stored in the request
542 : 218278 : res.m_headers = std::move(m_response_headers);
543 : :
544 : : // Response version matches request version
545 : 218278 : res.m_version = m_version;
546 : :
547 : : // Add response code
548 : 218278 : res.m_status = status;
549 : :
550 : : // See libevent evhttp_response_needs_body()
551 : : // Response headers are different if no body is needed
552 : 218278 : bool needs_body{status != HTTP_NO_CONTENT && (status < 100 || status >= 200)};
553 : 218278 : bool needs_content_length{false};
554 : :
555 : 218278 : bool keep_alive{false};
556 : :
557 : : // See libevent evhttp_make_header_response()
558 : : // Expected response headers depend on protocol version
559 [ + - ]: 218278 : if (m_version.major == 1) {
560 : : // HTTP/1.0
561 [ - + ]: 218278 : if (m_version.minor == 0) {
562 [ # # ]: 0 : auto connection_header{m_headers.FindFirst("Connection")};
563 [ # # # # : 0 : if (connection_header && ToLower(connection_header.value()) == "keep-alive") {
# # # # #
# ]
564 [ # # # # : 0 : res.m_headers.Write("Connection", "keep-alive");
# # ]
565 : 0 : keep_alive = true;
566 : : // HTTP/1.0 connections are closed by default so EOF is sufficient
567 : : // to indicate end of the body. Adding Content-Length a special case.
568 [ # # ]: 0 : if (needs_body) needs_content_length = true;
569 : : }
570 : 0 : }
571 : :
572 : : // HTTP/1.1
573 [ + - ]: 218278 : if (m_version.minor >= 1) {
574 : 218278 : const int64_t now_seconds{TicksSinceEpoch<std::chrono::seconds>(NodeClock::now())};
575 [ + - + - : 436556 : res.m_headers.Write("Date", FormatRFC1123DateTime(now_seconds));
+ - ]
576 : :
577 : : // HTTP/1.1 connections are kept alive by default and always require Content-Length.
578 [ + + ]: 218278 : if (needs_body) needs_content_length = true;
579 : :
580 : : // Default for HTTP/1.1
581 : : keep_alive = true;
582 : : }
583 : : }
584 : :
585 [ - + ]: 3 : if (needs_content_length) {
586 [ + - + - : 436550 : res.m_headers.Write("Content-Length", util::ToString(reply_body.size()));
+ - ]
587 : : }
588 : :
589 [ + + + - : 436553 : if (needs_body && !res.m_headers.FindFirst("Content-Type")) {
+ + + + ]
590 : : // Default type from libevent evhttp_new_object()
591 [ + - + - : 1294 : res.m_headers.Write("Content-Type", "text/html; charset=ISO-8859-1");
+ - ]
592 : : }
593 : :
594 [ + - ]: 218278 : auto connection_header{m_headers.FindFirst("Connection")};
595 [ + + - + : 219297 : if (connection_header && ToLower(connection_header.value()) == "close") {
+ - + + +
+ ]
596 : : // Might not exist already but we need to replace it, not append to it
597 [ + - ]: 1014 : res.m_headers.RemoveAll("Connection");
598 : :
599 [ + - + - : 2028 : res.m_headers.Write("Connection", "close");
+ - ]
600 : 1014 : keep_alive = false;
601 : : }
602 : :
603 : 218278 : std::shared_ptr client{m_client.lock()};
604 [ - + - - ]: 218278 : if (!client) return;
605 : :
606 [ + - ]: 218278 : client->m_keep_alive = keep_alive;
607 : :
608 : : // Serialize the response headers
609 [ + - ]: 218278 : const std::string headers{res.StringifyHeaders()};
610 [ - + + - ]: 218278 : const auto headers_bytes{std::as_bytes(std::span{headers})};
611 : :
612 : 218278 : bool send_buffer_was_empty{false};
613 : : // Fill the send buffer with the complete serialized response headers + body
614 : 218278 : {
615 [ + - ]: 218278 : LOCK(client->m_send_mutex);
616 [ + - ]: 218278 : send_buffer_was_empty = client->m_send_buffer.empty();
617 [ + - ]: 218278 : client->m_send_buffer.insert(client->m_send_buffer.end(), headers_bytes.begin(), headers_bytes.end());
618 : :
619 : : // We've been using std::span up until now but it is finally time to copy
620 : : // data. The original data will go out of scope when WriteReply() returns.
621 : : // This is analogous to the memcpy() in libevent's evbuffer_add()
622 [ + - ]: 218278 : client->m_send_buffer.insert(client->m_send_buffer.end(), reply_body.begin(), reply_body.end());
623 : :
624 : : // If the buffer already held data, the I/O thread is (or soon will be)
625 : : // draining it, so flag that there is more data to send. This must happen
626 : : // while holding m_send_mutex and while the buffer is known non-empty:
627 : : // setting m_send_ready after releasing the lock would race with the I/O
628 : : // thread draining the buffer to empty and clearing m_send_ready in
629 : : // between, leaving m_send_ready set on an empty buffer. The I/O loop would
630 : : // then only ever poll the socket for writeability, never read the client's
631 : : // next request, and wedge the connection.
632 [ + + ]: 218278 : if (!send_buffer_was_empty) client->m_send_ready = true;
633 : 0 : }
634 : :
635 [ + - + + : 218278 : LogDebug(
+ - ]
636 : : BCLog::HTTP,
637 : : "HTTPResponse (status code: %d size: %lld) added to send buffer for client %s (id=%llu)",
638 : : status,
639 : : headers_bytes.size() + reply_body.size(),
640 : : client->m_origin,
641 : : client->m_id);
642 : :
643 : : // If the send buffer was empty before we wrote this reply, we can try an
644 : : // optimistic send akin to CConnman::PushMessage() in which we
645 : : // push the data directly out the socket to client right now, instead
646 : : // of waiting for the next iteration of the I/O loop.
647 [ + + ]: 218278 : if (send_buffer_was_empty) {
648 [ + - ]: 218277 : client->MaybeSendBytesFromBuffer();
649 : : }
650 : :
651 : : // Signal to the I/O loop that we are ready to handle the next request.
652 : 218278 : client->m_req_busy = false;
653 [ + - ]: 436556 : }
654 : :
655 : 216891 : CService HTTPRequest::GetPeer() const
656 : : {
657 [ + - ]: 216891 : if (std::shared_ptr c{m_client.lock()}) {
658 : 216891 : return c->m_addr;
659 : : } else {
660 [ # # ]: 0 : return {};
661 : 216891 : }
662 : : }
663 : :
664 : 93 : std::optional<std::string> HTTPRequest::GetQueryParameter(const std::string_view key) const
665 : : {
666 [ - + ]: 93 : return GetQueryParameterFromUri(m_target, key);
667 : : }
668 : :
669 : : // See libevent http.c evhttp_parse_query_impl()
670 : : // and https://www.rfc-editor.org/rfc/rfc3986#section-3.4
671 : 107 : std::optional<std::string> GetQueryParameterFromUri(const std::string_view uri, const std::string_view key)
672 : : {
673 : : // find query in URI
674 : 107 : size_t start = uri.find('?');
675 [ + + ]: 107 : if (start == std::string::npos) return std::nullopt;
676 : 97 : size_t end = uri.find('#', start);
677 [ + - ]: 97 : if (end == std::string::npos) {
678 : 97 : end = uri.length();
679 : : }
680 : 97 : const std::string_view query{uri.data() + start + 1, end - start - 1};
681 : : // find requested parameter in query
682 : 97 : const std::vector<std::string_view> params{Split<std::string_view>(query, "&")};
683 [ + + ]: 136 : for (const std::string_view& param : params) {
684 : 124 : size_t delim = param.find('=');
685 [ + - + - : 248 : if (key == UrlDecode(param.substr(0, delim))) {
+ + ]
686 [ - + ]: 85 : if (delim == std::string::npos) {
687 [ # # ]: 0 : return "";
688 : : } else {
689 [ + - + - ]: 170 : return std::string(UrlDecode(param.substr(delim + 1)));
690 : : }
691 : : }
692 : : }
693 : 12 : return std::nullopt;
694 : 97 : }
695 : :
696 : 216887 : std::pair<bool, std::string> HTTPRequest::GetHeader(const std::string_view hdr) const
697 : : {
698 : 216887 : std::optional<std::string> found{m_headers.FindFirst(hdr)};
699 [ + - ]: 433774 : return std::pair{found.has_value(), std::move(found).value_or("")};
700 : 216887 : }
701 : :
702 : 219036 : void HTTPRequest::WriteHeader(std::string&& hdr, std::string&& value)
703 : : {
704 : 219036 : m_response_headers.Write(std::move(hdr), std::move(value));
705 : 219036 : }
706 : :
707 : 2379 : util::Expected<void, std::string> HTTPServer::BindAndStartListening(const CService& to)
708 : : {
709 : : // Create socket for listening for incoming connections
710 : 2379 : sockaddr_storage storage;
711 : 2379 : auto sa = reinterpret_cast<sockaddr*>(&storage);
712 : 2379 : socklen_t len{sizeof(storage)};
713 [ + + ]: 2379 : if (!to.GetSockAddr(sa, &len)) {
714 [ + - ]: 2 : return util::Unexpected{strprintf("Bind address family for %s not supported", to.ToStringAddrPort())};
715 : : }
716 : :
717 : 2378 : std::unique_ptr<Sock> sock{CreateSock(to.GetSAFamily(), SOCK_STREAM, IPPROTO_TCP)};
718 [ - + ]: 2378 : if (!sock) {
719 [ # # ]: 0 : return util::Unexpected{strprintf("Cannot create %s listen socket: %s",
720 [ # # ]: 0 : to.ToStringAddrPort(),
721 [ # # ]: 0 : NetworkErrorString(WSAGetLastError()))};
722 : : }
723 : :
724 : : // Allow binding if the port is still in TIME_WAIT state after
725 : : // the program was closed and restarted.
726 [ + - - + ]: 2378 : if (sock->SetSockOpt(SOL_SOCKET, SO_REUSEADDR, &SOCKET_OPTION_TRUE, sizeof(SOCKET_OPTION_TRUE)) == SOCKET_ERROR) {
727 [ # # # # : 0 : LogDebug(BCLog::HTTP,
# # # # #
# ]
728 : : "Cannot set SO_REUSEADDR on %s listen socket: %s, continuing anyway",
729 : : to.ToStringAddrPort(),
730 : : NetworkErrorString(WSAGetLastError()));
731 : : }
732 : :
733 : : // some systems don't have IPV6_V6ONLY but are always v6only; others do have the option
734 : : // and enable it by default or not. Try to enable it, if possible.
735 [ + + ]: 2378 : if (to.IsIPv6()) {
736 : : #ifdef IPV6_V6ONLY
737 [ + - - + ]: 1182 : if (sock->SetSockOpt(IPPROTO_IPV6, IPV6_V6ONLY, &SOCKET_OPTION_TRUE, sizeof(SOCKET_OPTION_TRUE)) == SOCKET_ERROR) {
738 [ # # # # : 0 : LogDebug(BCLog::HTTP,
# # # # #
# ]
739 : : "Cannot set IPV6_V6ONLY on %s listen socket: %s, continuing anyway",
740 : : to.ToStringAddrPort(),
741 : : NetworkErrorString(WSAGetLastError()));
742 : : }
743 : : #endif
744 : : #ifdef WIN32
745 : : int prot_level{PROTECTION_LEVEL_UNRESTRICTED};
746 : : if (sock->SetSockOpt(IPPROTO_IPV6,
747 : : IPV6_PROTECTION_LEVEL,
748 : : &prot_level,
749 : : sizeof(prot_level)) == SOCKET_ERROR) {
750 : : LogDebug(BCLog::HTTP,
751 : : "Cannot set IPV6_PROTECTION_LEVEL on %s listen socket: %s, continuing anyway",
752 : : to.ToStringAddrPort(),
753 : : NetworkErrorString(WSAGetLastError()));
754 : : }
755 : : #endif
756 : : }
757 : :
758 [ + - - + ]: 2378 : if (sock->Bind(sa, len) == SOCKET_ERROR) {
759 : 0 : const int err{WSAGetLastError()};
760 [ # # ]: 0 : if (err == WSAEADDRINUSE) {
761 [ # # ]: 0 : return util::Unexpected{strprintf("Unable to bind to %s on this computer. %s is probably already running.",
762 [ # # ]: 0 : to.ToStringAddrPort(),
763 : 0 : CLIENT_NAME)};
764 : : } else {
765 [ # # ]: 0 : return util::Unexpected{strprintf("Unable to bind to %s on this computer (bind returned error %s)",
766 [ # # ]: 0 : to.ToStringAddrPort(),
767 [ # # ]: 0 : NetworkErrorString(err))};
768 : : }
769 : : }
770 : :
771 : : // Listen for incoming connections
772 [ + - - + ]: 2378 : if (sock->Listen(SOMAXCONN) == SOCKET_ERROR) {
773 [ # # ]: 0 : return util::Unexpected{strprintf("Cannot listen on %s: %s",
774 [ # # ]: 0 : to.ToStringAddrPort(),
775 [ # # ]: 0 : NetworkErrorString(WSAGetLastError()))};
776 : : }
777 : :
778 [ + - ]: 2378 : m_listen.emplace_back(std::move(sock));
779 : :
780 : 2378 : return {};
781 : 2378 : }
782 : :
783 : 1192 : void HTTPServer::StopListening()
784 : : {
785 : 1192 : m_listen.clear();
786 : 1192 : }
787 : :
788 : 1180 : void HTTPServer::StartSocketsThreads()
789 : : {
790 : : // The socket handler reads m_allow_subnets in ClientAllowed(). InitHTTPAllowList()
791 : : // must have populated it first; localhost entries are always added, so an empty
792 : : // list means it was never called and every connection is rejected.
793 : 1180 : Assume(!m_allow_subnets.empty());
794 : :
795 : 1180 : m_thread_socket_handler = std::thread(&util::TraceThread,
796 : : "http",
797 : 2360 : [this] { ThreadSocketHandler(); });
798 : 1180 : }
799 : :
800 : 1192 : void HTTPServer::JoinSocketsThreads()
801 : : {
802 [ + + ]: 1192 : if (m_thread_socket_handler.joinable()) {
803 : 1180 : m_thread_socket_handler.join();
804 : : }
805 : 1192 : }
806 : :
807 : 3117 : std::unique_ptr<Sock> HTTPServer::AcceptConnection(const Sock& listen_sock, CService& addr)
808 : : {
809 : : // Make sure we only operate on our own listening sockets
810 [ + + ]: 9342 : Assume(std::ranges::any_of(m_listen, [&](const auto& sock) { return sock.get() == &listen_sock; }));
811 : :
812 : 3117 : sockaddr_storage storage;
813 : 3117 : socklen_t len{sizeof(storage)};
814 : 3117 : auto sa = reinterpret_cast<sockaddr*>(&storage);
815 : :
816 : 3117 : auto sock{listen_sock.Accept(sa, &len)};
817 : :
818 [ - + ]: 3117 : if (!sock) {
819 : 0 : const int err{WSAGetLastError()};
820 [ # # ]: 0 : if (err != WSAEWOULDBLOCK) {
821 [ # # # # : 0 : LogDebug(BCLog::HTTP,
# # # # ]
822 : : "Cannot accept new connection: %s",
823 : : NetworkErrorString(err));
824 : : }
825 : 0 : return {};
826 : : }
827 : :
828 : : // The OS handed us a valid socket but we can't determine its source address.
829 [ + - - + ]: 3117 : if (!addr.SetSockAddr(sa, len)) {
830 [ # # # # : 0 : LogDebug(BCLog::HTTP,
# # ]
831 : : "Unknown socket family");
832 : : }
833 : :
834 : : // Early address-based allow check
835 [ + - + + ]: 3117 : if (!ClientAllowed(addr)) {
836 [ + - + - : 2 : LogDebug(BCLog::HTTP, "Connection from %s rejected: Client network is not allowed HTTP access\n",
+ - + - ]
837 : : addr.ToStringAddrPort());
838 : : // Socket destroyed, connection aborted
839 : 2 : return {};
840 : : }
841 : :
842 : 3115 : return sock;
843 : 3117 : }
844 : :
845 : 3115 : HTTPServer::Id HTTPServer::GetNewId()
846 : : {
847 : 3115 : return m_next_id.fetch_add(1, std::memory_order_relaxed);
848 : : }
849 : :
850 : 3115 : void HTTPServer::NewSockAccepted(std::unique_ptr<Sock>&& sock, const CService& addr)
851 : : {
852 [ - + ]: 3115 : if (!sock->IsSelectable()) {
853 [ # # # # ]: 0 : LogDebug(BCLog::HTTP,
854 : : "connection from %s dropped: non-selectable socket",
855 : : addr.ToStringAddrPort());
856 : 0 : return;
857 : : }
858 : :
859 : : // According to the internet TCP_NODELAY is not carried into accepted sockets
860 : : // on all platforms. Set it again here just to be sure.
861 [ - + ]: 3115 : if (sock->SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &SOCKET_OPTION_TRUE, sizeof(SOCKET_OPTION_TRUE)) == SOCKET_ERROR) {
862 [ # # # # ]: 0 : LogDebug(BCLog::HTTP, "connection from %s: unable to set TCP_NODELAY, continuing anyway",
863 : : addr.ToStringAddrPort());
864 : : }
865 : :
866 : 3115 : const Id id{GetNewId()};
867 : :
868 [ + - - + ]: 3115 : m_connected.push_back(std::make_shared<HTTPRemoteClient>(id, addr, std::move(sock)));
869 : : // Report back to the main thread
870 : 3115 : m_connected_size.fetch_add(1, std::memory_order_relaxed);
871 : :
872 [ + + + - ]: 3115 : LogDebug(BCLog::HTTP,
873 : : "HTTP Connection accepted from %s (id=%llu)",
874 : : addr.ToStringAddrPort(), id);
875 : : }
876 : :
877 : 589919 : void HTTPServer::SocketHandlerConnected(const IOReadiness& io_readiness) const
878 : : {
879 [ + + ]: 2566196 : for (const auto& [sock, events] : io_readiness.events_per_sock) {
880 [ + + ]: 1976277 : if (m_interrupt_net) {
881 : : return;
882 : : }
883 : :
884 : 1975102 : auto it{io_readiness.httpclients_per_sock.find(sock)};
885 [ + + ]: 1975102 : if (it == io_readiness.httpclients_per_sock.end()) {
886 : 1175272 : continue;
887 : : }
888 [ + + ]: 799830 : const std::shared_ptr<HTTPRemoteClient>& client{it->second};
889 : :
890 : 799830 : bool send_ready = events.occurred & Sock::SendEvent;
891 : 799830 : bool recv_ready = events.occurred & Sock::RecvEvent;
892 : 799830 : bool err_ready = events.occurred & Sock::ErrorEvent;
893 : :
894 [ + + ]: 799830 : if (send_ready) {
895 : : // Try to send as much data as is ready for this client.
896 : : // If there's an error we can skip the receive phase for this client
897 : : // because we need to disconnect.
898 [ - + ]: 2171 : if (!client->MaybeSendBytesFromBuffer()) {
899 : 0 : recv_ready = false;
900 : : }
901 : : }
902 : :
903 [ + + ]: 799830 : if (recv_ready || err_ready) {
904 : 397019 : char buf[0x10000]; // typical socket buffer is 8K-64K
905 : :
906 [ + - + - ]: 1191057 : const ssize_t nrecv{WITH_LOCK(
907 : : client->m_sock_mutex,
908 : : return client->m_sock->Recv(buf, sizeof(buf), MSG_DONTWAIT);)};
909 : :
910 [ - + ]: 397019 : if (nrecv < 0) {
911 : 0 : const int err = WSAGetLastError();
912 [ # # ]: 0 : if (IOErrorIsPermanent(err)) {
913 [ # # # # ]: 0 : LogDebug(
914 : : BCLog::HTTP,
915 : : "Permanent read error from %s (id=%llu): %s",
916 : : client->m_origin,
917 : : client->m_id,
918 : : NetworkErrorString(err));
919 : 0 : client->m_disconnect = true;
920 : : }
921 [ + + ]: 397019 : } else if (nrecv == 0) {
922 [ + - ]: 1932 : LogDebug(
923 : : BCLog::HTTP,
924 : : "Received EOF from %s (id=%llu)",
925 : : client->m_origin,
926 : : client->m_id);
927 : 1932 : client->m_disconnect = true;
928 : : } else {
929 : : // Reset idle timeout
930 : 395087 : client->m_idle_since = Now<SteadySeconds>();
931 : :
932 : : // Prevent disconnect until all requests are completely handled.
933 [ - + ]: 395087 : client->m_connection_busy = true;
934 : :
935 : : // Copy data from socket buffer to client receive buffer
936 : 395087 : client->m_recv_buffer.insert(
937 : 395087 : client->m_recv_buffer.end(),
938 : : buf,
939 [ - + ]: 395087 : buf + nrecv);
940 : : }
941 : : }
942 : : // Process as much received data as we can.
943 : : // This executes for every client whether or not reading or writing
944 : : // took place because it also (might) parse a request we have already
945 : : // received and pass it to a worker thread.
946 : 799830 : MaybeDispatchRequestsFromClient(client);
947 : : }
948 : : }
949 : :
950 : 589919 : void HTTPServer::SocketHandlerListening(const Sock::EventsPerSock& events_per_sock)
951 : : {
952 [ + + ]: 589919 : if (m_stop_accepting) return;
953 [ + + ]: 1760885 : for (const auto& sock : m_listen) {
954 [ + + ]: 1173187 : if (m_interrupt_net) {
955 : : return;
956 : : }
957 [ + - + - ]: 2346368 : const auto it = events_per_sock.find(sock);
958 [ + - + + ]: 1176301 : if (it != events_per_sock.end() && it->second.occurred & Sock::RecvEvent) {
959 : 3117 : CService addr_accepted;
960 : :
961 [ + - ]: 3117 : auto sock_accepted{AcceptConnection(*sock, addr_accepted)};
962 : :
963 [ + + ]: 3117 : if (sock_accepted) {
964 [ + - ]: 3115 : NewSockAccepted(std::move(sock_accepted), addr_accepted);
965 : : }
966 : 3117 : }
967 : : }
968 : : }
969 : :
970 : 589919 : HTTPServer::IOReadiness HTTPServer::GenerateWaitSockets() const
971 : : {
972 : 589919 : IOReadiness io_readiness;
973 : :
974 [ + + ]: 1767535 : for (const auto& sock : m_listen) {
975 [ + - ]: 1177616 : io_readiness.events_per_sock.emplace(sock, Sock::Events{Sock::RecvEvent});
976 : : }
977 : :
978 [ + + ]: 1389750 : for (const auto& http_client : m_connected) {
979 : : // Safely copy the shared pointer to the socket
980 [ + - + - : 2399493 : std::shared_ptr<Sock> sock{WITH_LOCK(http_client->m_sock_mutex, return http_client->m_sock;)};
+ - ]
981 : :
982 : : // Check if client is ready to send data. Don't try to receive again
983 : : // until the send buffer is cleared (all data sent to client).
984 : : // Keep this as a separate critical section from the m_sock_mutex one above:
985 : : // never hold m_sock_mutex and m_send_mutex at the same time here.
986 : : // MaybeSendBytesFromBuffer() locks m_send_mutex then m_sock_mutex, so nesting
987 : : // them in the opposite order here would risk a lock-order inversion deadlock.
988 [ + - + - ]: 1599662 : const bool send_ready{WITH_LOCK(http_client->m_send_mutex, return http_client->m_send_ready;)};
989 [ + + ]: 799831 : Sock::Event event = (send_ready ? Sock::SendEvent : Sock::RecvEvent);
990 [ + - ]: 799831 : io_readiness.events_per_sock.emplace(sock, Sock::Events{event});
991 [ + - + - ]: 799831 : io_readiness.httpclients_per_sock.emplace(sock, http_client);
992 : 799831 : }
993 : :
994 : 589919 : return io_readiness;
995 : 0 : }
996 : :
997 : : /// \anchor http
998 : 1180 : void HTTPServer::ThreadSocketHandler()
999 : : {
1000 [ + + ]: 591099 : while (!m_interrupt_net) {
1001 : : // Check for the readiness of the already connected sockets and the
1002 : : // listening sockets in one call ("readiness" as in poll(2) or
1003 : : // select(2)). If none are ready, wait for a short while and return
1004 : : // empty sets.
1005 : 589919 : auto io_readiness{GenerateWaitSockets()};
1006 [ + - - + ]: 1179838 : if (io_readiness.events_per_sock.empty() ||
1007 : : // WaitMany() may as well be a static method, the context of the first Sock in the vector is not relevant.
1008 [ + - ]: 589919 : !io_readiness.events_per_sock.begin()->first->WaitMany(SELECT_TIMEOUT,
1009 : : io_readiness.events_per_sock)) {
1010 [ # # ]: 0 : m_interrupt_net.sleep_for(SELECT_TIMEOUT);
1011 : : }
1012 : :
1013 : : // Service (send/receive) each of the already connected sockets.
1014 [ + - ]: 589919 : SocketHandlerConnected(io_readiness);
1015 : :
1016 : : // Accept new connections from listening sockets.
1017 [ + - ]: 589919 : SocketHandlerListening(io_readiness.events_per_sock);
1018 : :
1019 : : // Disconnect any clients that have been flagged.
1020 [ + - ]: 589919 : DisconnectClients();
1021 : 589919 : }
1022 : 1180 : }
1023 : :
1024 : 799830 : void HTTPServer::MaybeDispatchRequestsFromClient(const std::shared_ptr<HTTPRemoteClient>& client) const
1025 : : {
1026 : : // If we are already handling a request from
1027 : : // this client, do nothing. We'll check again on the next I/O
1028 : : // loop iteration.
1029 [ + + ]: 799830 : if (client->m_req_busy) return;
1030 : :
1031 [ + + ]: 665984 : if (!client->m_req) {
1032 : 221286 : client->m_req = std::make_unique<HTTPRequest>(client);
1033 : : }
1034 : :
1035 : 665984 : try {
1036 : : // Read data from the buffer into the current request
1037 [ + + ]: 665984 : client->ReadRequest(*client->m_req);
1038 [ - + + ]: 13 : } catch (const ContentTooLargeError& e) {
1039 [ + - + - : 2 : LogDebug(
+ - ]
1040 : : BCLog::HTTP,
1041 : : "HTTP request body too large from client %s (id=%llu): %s",
1042 : : client->m_origin,
1043 : : client->m_id,
1044 : : e.what());
1045 : :
1046 [ + - ]: 2 : WriteNoStoreErrorReply(*client->m_req, HTTP_CONTENT_TOO_LARGE);
1047 : 2 : client->m_disconnect = true;
1048 : 2 : return;
1049 : 13 : } catch (const std::runtime_error& e) {
1050 [ + - + - : 11 : LogDebug(
+ - ]
1051 : : BCLog::HTTP,
1052 : : "Error reading HTTP request from client %s (id=%llu): %s",
1053 : : client->m_origin,
1054 : : client->m_id,
1055 : : e.what());
1056 : :
1057 : : // We failed to read a complete request from the buffer
1058 [ + - ]: 11 : WriteNoStoreErrorReply(*client->m_req, HTTP_BAD_REQUEST);
1059 : 11 : client->m_disconnect = true;
1060 : 11 : return;
1061 : 11 : }
1062 : :
1063 : : // If the request is ready, hand it to a worker.
1064 [ + + ]: 665971 : if (client->m_req->GetState() == HTTPRequest::State::Complete) {
1065 [ + + ]: 218265 : LogDebug(
1066 : : BCLog::HTTP,
1067 : : "Received a %s request for %s from %s (id=%llu)",
1068 : : RequestMethodString(client->m_req->m_method),
1069 : : client->m_req->m_target,
1070 : : client->m_origin,
1071 : : client->m_id);
1072 : :
1073 : 218265 : LOCK(m_request_dispatcher_mutex);
1074 [ + - ]: 218265 : client->m_req_busy = true;
1075 [ + - ]: 218265 : m_request_dispatcher(std::move(client->m_req));
1076 : 218265 : }
1077 : : }
1078 : :
1079 : 589919 : void HTTPServer::DisconnectClients()
1080 : : {
1081 : 589919 : const auto now{Now<SteadySeconds>()};
1082 : 589919 : size_t erased = std::erase_if(m_connected,
1083 : 802946 : [&](auto& client) {
1084 : : // First check for idle timeout. We reset the timer when we send and receive data,
1085 : : // but if the server is busy handling a request we should ignore the timeout until
1086 : : // the reply is sent. If we did erase the shared_ptr<HTTPRemoteClient> reference in m_connected
1087 : : // while the server is busy with a request, it might be prematurely dropped before
1088 : : // the response has been sent, or if the HTTPRequest was holding a temporary shared_ptr
1089 : : // client on a worker thread - it would keep the socket open even after "disconnecting".
1090 [ + - + + : 802966 : const bool is_idle{m_rpcservertimeout.count() > 0 &&
+ + ]
1091 [ + - + + : 802946 : now - client->m_idle_since.load() > m_rpcservertimeout &&
+ + ]
1092 [ + + ]: 20 : !client->m_req_busy};
1093 : :
1094 : : // Disconnect this client due to error, end of communication, or idle timeout.
1095 : : // May drop unsent data if we are closing due to error.
1096 [ + + + + ]: 802946 : if (client->m_disconnect || is_idle) {
1097 [ + + ]: 2073 : if (is_idle) {
1098 [ + - ]: 5 : LogDebug(BCLog::HTTP,
1099 : : "HTTP client idle timeout %s (id=%llu)",
1100 : : client->m_origin,
1101 : : client->m_id);
1102 : : }
1103 : : } else {
1104 : : // Disconnect this client because the server is shutting
1105 : : // down and we need to disconnect all clients...
1106 [ + + ]: 800873 : if (m_disconnect_all_clients) {
1107 : : // ...unless we still have data for this client.
1108 [ + - ]: 1042 : if (client->m_connection_busy) {
1109 : : // There is still data for this healthy-connected client.
1110 : : // Continue the I/O loop until all data is sent or an error is encountered.
1111 : : return false;
1112 : : } else {
1113 : : // This is a healthy persistent connection (e.g. keep-alive)
1114 : : // but it's time to say goodbye.
1115 : : ;
1116 : : }
1117 : : } else {
1118 : : // No reason to disconnect.
1119 : : return false;
1120 : : }
1121 : : }
1122 : : // No reason NOT to disconnect, log and remove.
1123 [ + + ]: 3115 : LogDebug(BCLog::HTTP,
1124 : : "Disconnecting HTTP client %s (id=%llu)",
1125 : : client->m_origin,
1126 : : client->m_id);
1127 : : return true;
1128 : : });
1129 [ + + ]: 589919 : if (erased > 0) {
1130 : : // Report back to the main thread
1131 : 3030 : m_connected_size.fetch_sub(erased, std::memory_order_relaxed);
1132 : : }
1133 : 589919 : }
1134 : :
1135 : 1189 : void HTTPServer::ClearConnectedClients()
1136 : : {
1137 [ - + ]: 1189 : Assume(!m_thread_socket_handler.joinable()); // must be called after JoinSocketsThreads()
1138 [ - + ]: 1189 : if (m_connected.empty()) return;
1139 [ # # ]: 0 : LogWarning("Force-disconnecting %d HTTP client(s) that did not disconnect gracefully", m_connected.size());
1140 [ # # ]: 0 : m_connected_size.fetch_sub(m_connected.size(), std::memory_order_relaxed);
1141 : 0 : m_connected.clear();
1142 : : }
1143 : :
1144 : 666018 : void HTTPRemoteClient::ReadRequest(HTTPRequest& req)
1145 : : {
1146 [ + + ]: 666018 : if (m_recv_buffer.empty()) return;
1147 : :
1148 [ - + ]: 393043 : LineReader reader(m_recv_buffer, MAX_HEADERS_SIZE);
1149 : :
1150 : 393043 : try {
1151 [ + + + + ]: 393043 : switch (req.GetState()) {
1152 : 218290 : case HTTPRequest::State::Init:
1153 [ + + + - ]: 218290 : if (!req.LoadControlData(reader)) break;
1154 : 218283 : req.SetState(HTTPRequest::State::NeedsHeaders);
1155 : 218288 : [[fallthrough]];
1156 : :
1157 : 218288 : case HTTPRequest::State::NeedsHeaders:
1158 [ + + + + ]: 218288 : if (!req.LoadHeaders(reader)) break;
1159 : 218277 : req.SetState(HTTPRequest::State::NeedsBody);
1160 : 393023 : [[fallthrough]];
1161 : :
1162 : 393023 : case HTTPRequest::State::NeedsBody:
1163 [ + + + + ]: 393023 : if (!req.LoadBody(reader)) break;
1164 : 218272 : req.SetState(HTTPRequest::State::Complete);
1165 : : [[fallthrough]];
1166 : :
1167 : : case HTTPRequest::State::Complete:
1168 : : break;
1169 : :
1170 : : case HTTPRequest::State::Error:
1171 : : break;
1172 : : }
1173 : 17 : } catch (...) {
1174 : : // Don't try to read any more data for this request
1175 : 17 : req.SetState(HTTPRequest::State::Error);
1176 : : // Clear the memory allocated to this client, caller must disconnect
1177 : 17 : m_recv_buffer.clear();
1178 : 17 : throw;
1179 : 17 : }
1180 : :
1181 : : // Remove the bytes read out of the buffer.
1182 : 786052 : m_recv_buffer.erase(
1183 : 393026 : m_recv_buffer.begin(),
1184 : 393026 : m_recv_buffer.begin() + reader.Consumed());
1185 : : }
1186 : :
1187 : 220448 : bool HTTPRemoteClient::MaybeSendBytesFromBuffer()
1188 : : {
1189 : : // Send as much data from this client's buffer as we can
1190 : 220448 : LOCK(m_send_mutex);
1191 [ + - ]: 220448 : if (!m_send_buffer.empty()) {
1192 : : // Socket flags (See kernel docs for send(2) and tcp(7) for more details).
1193 : : // MSG_NOSIGNAL: If the remote end of the connection is closed,
1194 : : // fail with EPIPE (an error) as opposed to triggering
1195 : : // SIGPIPE which terminates the process.
1196 : : // MSG_DONTWAIT: Makes the send operation non-blocking regardless of socket blocking mode.
1197 : : // MSG_MORE: We do not set this flag here because http responses are usually
1198 : : // small and we want the kernel to send them right away. Setting MSG_MORE
1199 : : // would "cork" the socket to prevent sending out partial frames.
1200 : 220448 : int flags{MSG_NOSIGNAL | MSG_DONTWAIT};
1201 : :
1202 : : // Try to send bytes through socket
1203 : 220448 : ssize_t bytes_sent;
1204 : 220448 : {
1205 [ + - ]: 220448 : LOCK(m_sock_mutex);
1206 [ - + + - : 220448 : bytes_sent = m_sock->Send(m_send_buffer.data(),
+ - ]
1207 : : m_send_buffer.size(),
1208 : : flags);
1209 : 0 : }
1210 : :
1211 [ + + ]: 220448 : if (bytes_sent < 0) {
1212 : : // Something went wrong
1213 : 2159 : const int err{WSAGetLastError()};
1214 [ + - ]: 2159 : if (!IOErrorIsPermanent(err)) {
1215 : : // The error can be safely ignored, try the send again on the next I/O loop.
1216 : 2159 : m_send_ready = true;
1217 : 2159 : m_connection_busy = true;
1218 : 2159 : return true;
1219 : : } else {
1220 : : // Unrecoverable error, log and disconnect client.
1221 [ # # # # : 0 : LogDebug(
# # # # ]
1222 : : BCLog::HTTP,
1223 : : "Error sending HTTP response data to client %s (id=%llu): %s",
1224 : : m_origin,
1225 : : m_id,
1226 : : NetworkErrorString(err));
1227 : 0 : m_send_ready = false;
1228 : 0 : m_disconnect = true;
1229 : :
1230 : : // Do not attempt to read from this client.
1231 : 0 : return false;
1232 : : }
1233 : : }
1234 : :
1235 : : // Successful send, remove sent bytes from our local buffer.
1236 [ - + ]: 218289 : Assume(static_cast<size_t>(bytes_sent) <= m_send_buffer.size());
1237 : 218289 : m_send_buffer.erase(m_send_buffer.begin(),
1238 : 218289 : m_send_buffer.begin() + bytes_sent);
1239 : :
1240 [ + - + + : 218289 : LogDebug(
+ - ]
1241 : : BCLog::HTTP,
1242 : : "Sent %d bytes to client %s (id=%llu)",
1243 : : bytes_sent,
1244 : : m_origin,
1245 : : m_id);
1246 : :
1247 : : // This check is inside the if(!empty) block meaning "there was data but now its gone".
1248 : : // We wouldn't want to change the flags if MaybeSendBytesFromBuffer() was called
1249 : : // on an already-empty m_send_buffer because the connection might have just been opened.
1250 [ + + ]: 218289 : if (m_send_buffer.empty()) {
1251 : 218277 : m_send_ready = false;
1252 [ + + ]: 218277 : m_connection_busy = false;
1253 : :
1254 : : // Our work is done here
1255 [ + + ]: 218277 : if (!m_keep_alive) {
1256 : 1014 : m_disconnect = true;
1257 : : // Do not attempt to read from this client.
1258 : 1014 : return false;
1259 : : }
1260 : : } else {
1261 : : // The send buffer isn't flushed yet, try to push more on the next loop.
1262 : 12 : m_send_ready = true;
1263 : 12 : m_connection_busy = true;
1264 : : }
1265 : :
1266 : : // Finally, reset idle timeout
1267 : 217275 : m_idle_since = Now<SteadySeconds>();
1268 : : }
1269 : :
1270 : : return true;
1271 : 220448 : }
1272 : :
1273 : 1189 : bool InitHTTPServer()
1274 : : {
1275 : : // Create HTTPServer
1276 : 1189 : g_http_server = std::make_unique<HTTPServer>(MaybeDispatchRequestToWorker);
1277 : :
1278 [ + - ]: 1189 : if (!g_http_server->InitHTTPAllowList()) {
1279 : : return false;
1280 : : }
1281 : :
1282 : 2378 : g_http_server->SetServerTimeout(std::chrono::seconds(gArgs.GetIntArg("-rpcservertimeout", DEFAULT_HTTP_SERVER_TIMEOUT)));
1283 : :
1284 : : // Bind HTTP server to specified addresses
1285 : 1189 : std::vector<std::pair<std::string, uint16_t>> endpoints{GetBindAddresses()};
1286 : 1189 : bool bind_success{false};
1287 [ + - + + ]: 3564 : for (const auto& [address_string, port] : endpoints) {
1288 [ + - ]: 2375 : LogInfo("Binding RPC on address %s port %i", address_string, port);
1289 [ + - + - ]: 2375 : const std::optional<CService> addr{Lookup(address_string, port, false)};
1290 [ + - ]: 2375 : if (addr) {
1291 [ + - - + ]: 2375 : if (addr->IsBindAny()) {
1292 [ # # ]: 0 : LogWarning("The RPC server is not safe to expose to untrusted networks such as the public internet");
1293 : : }
1294 [ + - + - ]: 2375 : auto result{g_http_server->BindAndStartListening(addr.value())};
1295 [ - + ]: 2375 : if (!result) {
1296 [ # # # # ]: 0 : LogWarning("Binding RPC on address %s failed: %s", addr->ToStringAddrPort(), result.error());
1297 : : } else {
1298 : : bind_success = true;
1299 : : }
1300 : 2375 : } else {
1301 [ - - ]: 2375 : LogWarning("Could not bind RPC on address %s port %i: Address lookup failed.", address_string, port);
1302 : : }
1303 : 2375 : }
1304 : :
1305 [ - + ]: 1189 : if (!bind_success) {
1306 [ # # ]: 0 : LogError("Unable to bind any endpoint for RPC server");
1307 : : return false;
1308 : : }
1309 : :
1310 [ + - + + : 1189 : LogDebug(BCLog::HTTP, "Initialized HTTP server");
+ - ]
1311 : :
1312 [ + - + - : 2378 : g_max_queue_depth = std::max(gArgs.GetArg<int>("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1);
+ - ]
1313 [ + - + + : 1189 : LogDebug(BCLog::HTTP, "set work queue of depth %d\n", g_max_queue_depth);
+ - ]
1314 : :
1315 : : return true;
1316 : 1189 : }
1317 : :
1318 : 1177 : void StartHTTPServer()
1319 : : {
1320 [ + - + - ]: 2354 : auto rpcThreads{std::max(gArgs.GetArg<int>("-rpcthreads", DEFAULT_HTTP_THREADS), 1)};
1321 : 1177 : LogInfo("Starting HTTP server with %d worker threads", rpcThreads);
1322 : 1177 : g_threadpool_http.Start(rpcThreads);
1323 : 1177 : g_http_server->StartSocketsThreads();
1324 : 1177 : }
1325 : :
1326 : 1231 : void InterruptHTTPServer()
1327 : : {
1328 [ + + ]: 1231 : LogDebug(BCLog::HTTP, "Interrupting HTTP server");
1329 [ + + ]: 1231 : if (g_http_server) {
1330 : : // Reject all new requests
1331 [ + - ]: 2378 : g_http_server->SetRequestHandler(RejectRequest);
1332 : : }
1333 : :
1334 : : // Interrupt pool after disabling requests
1335 : 1231 : g_threadpool_http.Interrupt();
1336 : 1231 : }
1337 : :
1338 : 1231 : void StopHTTPServer()
1339 : : {
1340 [ + + ]: 1231 : LogDebug(BCLog::HTTP, "Stopping HTTP server");
1341 : :
1342 [ + + ]: 1231 : LogDebug(BCLog::HTTP, "Waiting for HTTP worker threads to exit\n");
1343 : 1231 : g_threadpool_http.Stop();
1344 : :
1345 [ + + ]: 1231 : if (g_http_server) {
1346 : : // Must precede DisconnectAllClients(): a connection accepted after
1347 : : // GetConnectionsCount() returns 0 would survive into the destructor.
1348 : 1189 : g_http_server->StopAccepting();
1349 : : // Disconnect clients as their remaining responses are flushed
1350 : 1189 : g_http_server->DisconnectAllClients();
1351 : : // Wait 30 seconds for all disconnections
1352 [ + + ]: 1189 : LogDebug(BCLog::HTTP, "Waiting for HTTP clients to disconnect gracefully");
1353 : 1189 : const auto deadline{NodeClock::now() + 30s};
1354 [ + + ]: 3469 : while (g_http_server->GetConnectionsCount() != 0) {
1355 [ - + ]: 1091 : if (NodeClock::now() > deadline) {
1356 : 0 : LogWarning("Timeout waiting for HTTP clients to disconnect gracefully, continuing shutdown");
1357 : 0 : break;
1358 : : }
1359 : 1091 : std::this_thread::sleep_for(50ms);
1360 : : }
1361 : : // Break HTTPServer I/O loop: stop accepting connections, sending and receiving data
1362 : 1189 : g_http_server->InterruptNet();
1363 : : // Wait for HTTPServer I/O thread to exit
1364 : 1189 : g_http_server->JoinSocketsThreads();
1365 : : // Force-remove any clients that survived the graceful wait
1366 : 1189 : g_http_server->ClearConnectedClients();
1367 : : // Close all listening sockets
1368 : 1189 : g_http_server->StopListening();
1369 : : }
1370 [ + + ]: 1231 : LogDebug(BCLog::HTTP, "Stopped HTTP server");
1371 : 1231 : }
1372 : : } // namespace http_bitcoin
|