diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 376a269a800..3f271da389e 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -129,13 +129,19 @@ std::string_view RequestMethodString(HTTPRequestMethod m) assert(false); } +static void WriteNoStoreErrorReply(HTTPRequest& req, HTTPStatusCode status, std::string_view reply = {}) +{ + req.WriteHeader("Cache-Control", "no-store"); + req.WriteReply(status, reply); +} + static void MaybeDispatchRequestToWorker(std::shared_ptr hreq) { // Early reject unknown HTTP methods if (hreq->GetRequestMethod() == HTTPRequestMethod::UNKNOWN) { LogDebug(BCLog::HTTP, "HTTP request from %s rejected: Unknown HTTP request method\n", hreq->GetPeer().ToStringAddrPort()); - hreq->WriteReply(HTTP_BAD_METHOD); + WriteNoStoreErrorReply(*hreq, HTTP_BAD_METHOD); return; } @@ -161,7 +167,7 @@ static void MaybeDispatchRequestToWorker(std::shared_ptr hreq) if (i != iend) { if (static_cast(g_threadpool_http.WorkQueueSize()) >= g_max_queue_depth) { LogWarning("Request rejected because http work queue depth exceeded, it can be increased with the -rpcworkqueue= setting"); - hreq->WriteReply(HTTP_SERVICE_UNAVAILABLE, "Work queue depth exceeded"); + WriteNoStoreErrorReply(*hreq, HTTP_SERVICE_UNAVAILABLE, "Work queue depth exceeded"); return; } @@ -180,25 +186,25 @@ static void MaybeDispatchRequestToWorker(std::shared_ptr hreq) // Reply so the client doesn't hang waiting for the response. req->WriteHeader("Connection", "close"); // TODO: Implement specific error formatting for the REST and JSON-RPC servers responses. - req->WriteReply(HTTP_INTERNAL_SERVER_ERROR, err_msg); + WriteNoStoreErrorReply(*req, HTTP_INTERNAL_SERVER_ERROR, err_msg); }; if (auto res = g_threadpool_http.Submit(std::move(item)); !res.has_value()) { Assume(hreq.use_count() == 1); // ensure request will be deleted // Both SubmitError::Inactive and SubmitError::Interrupted mean shutdown LogWarning("HTTP request rejected during server shutdown: '%s'", SubmitErrorString(res.error())); - hreq->WriteReply(HTTP_SERVICE_UNAVAILABLE, "Request rejected during server shutdown"); + WriteNoStoreErrorReply(*hreq, HTTP_SERVICE_UNAVAILABLE, "Request rejected during server shutdown"); return; } } else { - hreq->WriteReply(HTTP_NOT_FOUND); + WriteNoStoreErrorReply(*hreq, HTTP_NOT_FOUND); } } static void RejectRequest(std::unique_ptr hreq) { LogDebug(BCLog::HTTP, "Rejecting request while shutting down"); - hreq->WriteReply(HTTP_SERVICE_UNAVAILABLE); + WriteNoStoreErrorReply(*hreq, HTTP_SERVICE_UNAVAILABLE); } static std::vector> GetBindAddresses() @@ -1005,7 +1011,7 @@ void HTTPServer::MaybeDispatchRequestsFromClient(const std::shared_ptrm_id, e.what()); - req->WriteReply(HTTP_CONTENT_TOO_LARGE); + WriteNoStoreErrorReply(*req, HTTP_CONTENT_TOO_LARGE); client->m_disconnect = true; return; } catch (const std::runtime_error& e) { @@ -1017,7 +1023,7 @@ void HTTPServer::MaybeDispatchRequestsFromClient(const std::shared_ptrWriteReply(HTTP_BAD_REQUEST); + WriteNoStoreErrorReply(*req, HTTP_BAD_REQUEST); client->m_disconnect = true; return; } diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index fd7bd34a957..5c6d86df8ee 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -635,6 +635,9 @@ class RESTTest (BitcoinTestFramework): assert_cache_control("/blockhashbyheight/999999999", no_store, status=404) assert_cache_control(f"/block/{UNKNOWN_PARAM}", no_store, status=404) assert_cache_control(f"/tx/{'f' * 64}", no_store, status=404) + assert_cache_control("", no_store, status=404, query_params={"x": 1}, req_type=None) + assert_cache_control("/tx", no_store, status=404, req_type=None) + assert_cache_control("/does-not-exist", no_store, status=404, req_type=None) assert_cache_control("/mempool/not-a-valid-path", no_store, status=400) assert_cache_control(f"/deploymentinfo/{non_existing_blockhash}", no_store, status=400)