Close established HTTP requests when trying to shut down (#1308)

Currently we only close the listening HTTP socket(s) but not established
connections, which means a client make repeated keep-alive requests will
keep the HTTP server alive for potentially a very long time when we're
trying to shut down or restart.

This commit changes the logic to more forcefully close request
connections with the next reply, so that we properly shut down within a
few seconds but give clients a chance to finish up their current
request.
This commit is contained in:
Jason Rhinelander 2020-10-08 19:48:24 -03:00 committed by GitHub
parent e2068a5a33
commit d16899104e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 18 additions and 0 deletions

View file

@ -249,6 +249,7 @@ namespace cryptonote::rpc {
auto& res = data->res;
res.writeHeader("Server", data->http.server_header());
res.writeHeader("Content-Type", data->call->is_binary ? "application/octet-stream"sv : "application/json"sv);
if (data->http.closing()) res.writeHeader("Connection", "close");
for (const auto& [name, value] : data->extra_headers)
res.writeHeader(name, value);
@ -256,6 +257,7 @@ namespace cryptonote::rpc {
res.write(piece);
res.end();
if (data->http.closing()) res.close();
});
});
}
@ -600,6 +602,8 @@ namespace cryptonote::rpc {
for (auto* s : m_listen_socks)
us_listen_socket_close(/*ssl=*/false, s);
m_closing = true;
{
// Destroy any pending long poll connections as well
MTRACE("closing pending long poll requests");

View file

@ -48,8 +48,10 @@ namespace cryptonote::rpc {
res.writeHeader("Server", m_server_header);
res.writeHeader("WWW-Authenticate", *www_auth);
res.writeHeader("Content-Type", "text/plain");
if (m_closing) res.writeHeader("Connection", "close");
if (req.getMethod() != "HEAD"sv)
res.end("Login required\n");
if (m_closing) res.close();
return false;
}
return true;
@ -63,10 +65,12 @@ namespace cryptonote::rpc {
res.writeStatus(std::to_string(code.first) + " " + std::string{code.second});
res.writeHeader("Server", m_server_header);
res.writeHeader("Content-Type", "text/plain");
if (m_closing) res.writeHeader("Connection", "close");
if (body)
res.end(*body);
else
res.end(std::string{code.second} + "\n");
if (m_closing) res.close();
}
// Similar to the above, but for JSON errors (which are 200 OK + error embedded in JSON)
@ -85,7 +89,9 @@ namespace cryptonote::rpc {
res.writeStatus("200 OK"sv);
res.writeHeader("Server", m_server_header);
res.writeHeader("Content-Type", "application/json");
if (m_closing) res.writeHeader("Connection", "close");
res.end(body);
if (m_closing) res.close();
}
std::string http_server_base::get_remote_address(HttpResponse& res) {

View file

@ -52,6 +52,8 @@ namespace cryptonote::rpc {
const std::string& server_header() { return m_server_header; }
bool closing() const { return m_closing; }
static constexpr http_response_code
HTTP_OK{200, "OK"sv},
HTTP_BAD_REQUEST{400, "Bad Request"sv},
@ -80,6 +82,9 @@ namespace cryptonote::rpc {
// Access-Control-Allow-Origin header values; if one of these match the incoming Origin header
// we return it in the ACAO header; otherwise (or if this is empty) we omit the header entirely.
std::unordered_set<std::string> m_cors;
// Will be set to true when we're trying to shut down which closes any connections as we reply
// to them. Should only be read/write from inside the uWS loop.
bool m_closing = false;
// If true then always reply with 'Access-Control-Allow-Origin: *' to allow anything.
bool m_cors_any = false;
};

View file

@ -311,8 +311,10 @@ namespace tools
res.writeHeader("Content-Type", "application/json");
for (const auto& [name, value] : extra_headers)
res.writeHeader(name, value);
if (closing()) res.writeHeader("Connection", "close");
res.end(result);
if (closing()) res.close();
});
}
@ -415,6 +417,7 @@ namespace tools
// Stopped: close the sockets, cancel the long poll, and rejoin the threads
for (auto* s : m_listen_socks)
us_listen_socket_close(/*ssl=*/false, s);
m_closing = true;
stop_long_poll_thread();