From 087483efb8dbffca61451334ffeaed550248e243 Mon Sep 17 00:00:00 2001 From: Igor Gaponenko Date: Thu, 6 Jul 2023 03:32:02 +0000 Subject: [PATCH] Added symbolic definitions for known HTTP status codes in qhttp Also migrated clients of the module to use these definitions instead of numeric literals ("magic" constants). --- src/qhttp/README.md | 2 +- src/qhttp/Response.cc | 124 ++++++++++++++-------------- src/qhttp/Response.h | 7 +- src/qhttp/Server.cc | 15 ++-- src/qhttp/StaticContent.cc | 9 +- src/qhttp/Status.h | 94 +++++++++++++++++++++ src/qhttp/testqhttp.cc | 142 +++++++++++++++++--------------- src/replica/testHttpAsyncReq.cc | 20 +++-- 8 files changed, 263 insertions(+), 150 deletions(-) create mode 100644 src/qhttp/Status.h diff --git a/src/qhttp/README.md b/src/qhttp/README.md index 7a3dd70f86..43130b60cc 100644 --- a/src/qhttp/README.md +++ b/src/qhttp/README.md @@ -74,7 +74,7 @@ void getFoo(qhttp::Request::Ptr req, qhttp::Response::Ptr resp) string requestedFoo = req->params["foo"]; auto const& foo = fooMap.find(requestedFoo); if (foo == fooMap.end()) { - resp->sendStatus(404); // simple status response + resp->sendStatus(qhttp::STATUS_NOT_FOUND); // simple status response } else { resp->send(foo->second.toJSON(), "application/json"); } diff --git a/src/qhttp/Response.cc b/src/qhttp/Response.cc index 93376ba8bf..d8ca71aa53 100644 --- a/src/qhttp/Response.cc +++ b/src/qhttp/Response.cc @@ -43,71 +43,71 @@ namespace asio = boost::asio; namespace ip = boost::asio::ip; namespace fs = boost::filesystem; +using namespace lsst::qserv::qhttp; namespace { LOG_LOGGER _log = LOG_GET("lsst.qserv.qhttp"); -std::map responseStringsByCode = { - {100, "Continue"}, - {101, "Switching Protocols"}, - {102, "Processing"}, - {200, "OK"}, - {201, "Created"}, - {202, "Accepted"}, - {203, "Non-Authoritative Information"}, - {204, "No Content"}, - {205, "Reset Content"}, - {206, "Partial Content"}, - {207, "Multi-Status"}, - {208, "Already Reported"}, - {226, "IM Used"}, - {300, "Multiple Choices"}, - {301, "Moved Permanently"}, - {302, "Found"}, - {303, "See Other"}, - {304, "Not Modified"}, - {305, "Use Proxy"}, - {307, "Temporary Redirect"}, - {308, "Permanent Redirect"}, - {400, "Bad Request"}, - {401, "Unauthorized"}, - {402, "Payment Required"}, - {403, "Forbidden"}, - {404, "Not Found"}, - {405, "Method Not Allowed"}, - {406, "Not Acceptable"}, - {407, "Proxy Authentication Required"}, - {408, "Request Timeout"}, - {409, "Conflict"}, - {410, "Gone"}, - {411, "Length Required"}, - {412, "Precondition Failed"}, - {413, "Payload Too Large"}, - {414, "URI Too Long"}, - {415, "Unsupported Media Type"}, - {416, "Range Not Satisfiable"}, - {417, "Expectation Failed"}, - {421, "Misdirected Request"}, - {422, "Unprocessable Entity"}, - {423, "Locked"}, - {424, "Failed Dependency"}, - {426, "Upgrade Required"}, - {428, "Precondition Required"}, - {429, "Too Many Requests"}, - {431, "Request Header Fields Too Large"}, - {500, "Internal Server Error"}, - {501, "Not Implemented"}, - {502, "Bad Gateway"}, - {503, "Service Unavailable"}, - {504, "Gateway Timeout"}, - {505, "HTTP Version Not Supported"}, - {506, "Variant Also Negotiates"}, - {507, "Insufficient Storage"}, - {508, "Loop Detected"}, - {510, "Not Extended"}, - {511, "Network Authentication Required"}, -}; +std::map responseStringsByCode = { + {STATUS_CONTINUE, "Continue"}, + {STATUS_SWITCH_PROTOCOL, "Switching Protocols"}, + {STATUS_PROCESSING, "Processing"}, + {STATUS_OK, "OK"}, + {STATUS_CREATED, "Created"}, + {STATUS_ACCEPTED, "Accepted"}, + {STATUS_NON_AUTHORATIVE_INFO, "Non-Authoritative Information"}, + {STATUS_NO_CONTENT, "No Content"}, + {STATUS_RESET_CONTENT, "Reset Content"}, + {STATUS_PARTIAL_CONTENT, "Partial Content"}, + {STATUS_MULTI_STATUS, "Multi-Status"}, + {STATUS_ALREADY_REPORTED, "Already Reported"}, + {STATUS_IM_USED, "IM Used"}, + {STATUS_MULTIPLE_CHOICES, "Multiple Choices"}, + {STATUS_MOVED_PERM, "Moved Permanently"}, + {STATUS_FOUND, "Found"}, + {STATUS_SEE_OTHER, "See Other"}, + {STATUS_NOT_MODIFIED, "Not Modified"}, + {STATUS_USE_PROXY, "Use Proxy"}, + {STATUS_TEMP_REDIRECT, "Temporary Redirect"}, + {STATUS_PERM_REDIRECT, "Permanent Redirect"}, + {STATUS_BAD_REQ, "Bad Request"}, + {STATUS_UNAUTHORIZED, "Unauthorized"}, + {STATUS_PAYMENT_REQUIRED, "Payment Required"}, + {STATUS_FORBIDDEN, "Forbidden"}, + {STATUS_NOT_FOUND, "Not Found"}, + {STATUS_METHOD_NOT_ALLOWED, "Method Not Allowed"}, + {STATUS_NON_ACCEPTABLE, "Not Acceptable"}, + {STATUS_PROXY_AUTH_REQUIRED, "Proxy Authentication Required"}, + {STATUS_REQ_TIMEOUT, "Request Timeout"}, + {STATUS_CONFLICT, "Conflict"}, + {STATUS_GONE, "Gone"}, + {STATUS_LENGTH_REQUIRED, "Length Required"}, + {STATUS_PRECOND_FAILED, "Precondition Failed"}, + {STATUS_PAYLOAD_TOO_LARGE, "Payload Too Large"}, + {STATUS_URI_TOO_LONG, "URI Too Long"}, + {STATUS_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type"}, + {STATUS_RANGE_NOT_SATISFIABLE, "Range Not Satisfiable"}, + {STATUS_FAILED_EXPECT, "Expectation Failed"}, + {STATUS_MISREDIRECT_REQ, "Misdirected Request"}, + {STATUS_UNPROCESSIBLE, "Unprocessable Entity"}, + {STATUS_LOCKED, "Locked"}, + {STATUS_FAILED_DEP, "Failed Dependency"}, + {STATUS_UPGRADE_REQUIRED, "Upgrade Required"}, + {STATUS_PRECOND_REQUIRED, "Precondition Required"}, + {STATUS_TOO_MANY_REQS, "Too Many Requests"}, + {STATUS_REQ_HDR_FIELDS_TOO_LARGE, "Request Header Fields Too Large"}, + {STATUS_INTERNAL_SERVER_ERR, "Internal Server Error"}, + {STATUS_NOT_IMPL, "Not Implemented"}, + {STATUS_BAD_GATEWAY, "Bad Gateway"}, + {STATUS_SERVICE_UNAVAIL, "Service Unavailable"}, + {STATUS_GSATEWAY_TIMEOUT, "Gateway Timeout"}, + {STATUS_UNDSUPPORT_VERSION, "HTTP Version Not Supported"}, + {STATUS_VARIANT_NEGOTIATES, "Variant Also Negotiates"}, + {STATUS_NO_STORAGE, "Insufficient Storage"}, + {STATUS_LOOP, "Loop Detected"}, + {STATUS_NOT_EXTENDED, "Not Extended"}, + {STATUS_NET_AUTH_REQUIRED, "Network Authentication Required"}}; std::unordered_map contentTypesByExtension = { {".css", "text/css"}, {".gif", "image/gif"}, {".htm", "text/html"}, @@ -125,7 +125,7 @@ Response::Response(std::shared_ptr const server, std::shared_ptrstatus = status; std::string statusStr = responseStringsByCode[status]; std::ostringstream entStr; @@ -171,7 +171,7 @@ std::string Response::_headers() const { headerst << "HTTP/1.1 "; auto r = responseStringsByCode.find(status); - if (r == responseStringsByCode.end()) r = responseStringsByCode.find(500); + if (r == responseStringsByCode.end()) r = responseStringsByCode.find(STATUS_INTERNAL_SERVER_ERR); headerst << r->first << " " << r->second; auto ilength = headers.find("Content-Length"); diff --git a/src/qhttp/Response.h b/src/qhttp/Response.h index 117cda4ac1..4fb91d6d40 100644 --- a/src/qhttp/Response.h +++ b/src/qhttp/Response.h @@ -35,6 +35,9 @@ #include "boost/asio.hpp" #include "boost/filesystem.hpp" +// Local headers +#include "qhttp/Status.h" + namespace lsst::qserv::qhttp { class Server; @@ -49,14 +52,14 @@ class Response : public std::enable_shared_from_this { // near the top of Response.cc for specific extensions supported.) void send(std::string const& content, std::string const& contentType = "text/html"); - void sendStatus(unsigned int status); + void sendStatus(Status status); void sendFile(boost::filesystem::path const& path); //----- Response status code and additional headers may also be set with these members, and will be // included/observed by the send methods above (sendStatus and sendFile will override status set // here, though; sendFile will override any Content-Type header set here.) - unsigned int status = {200}; + Status status = STATUS_OK; std::unordered_map headers; private: diff --git a/src/qhttp/Server.cc b/src/qhttp/Server.cc index 104d1369c5..254f3dabdc 100644 --- a/src/qhttp/Server.cc +++ b/src/qhttp/Server.cc @@ -40,6 +40,7 @@ #include "qhttp/AjaxEndpoint.h" #include "qhttp/LogHelpers.h" #include "qhttp/StaticContent.h" +#include "qhttp/Status.h" namespace asio = boost::asio; namespace errc = boost::system::errc; @@ -229,7 +230,7 @@ void Server::_readRequest(std::shared_ptr socket) { if (!(request->_parseHeader() && request->_parseUri())) { timer->cancel(); - response->sendStatus(400); + response->sendStatus(STATUS_BAD_REQ); return; } @@ -249,7 +250,7 @@ void Server::_readRequest(std::shared_ptr socket) { << "rejecting request with bad Content-Length: " << ctrlquote(request->header["Content-Length"])); timer->cancel(); - response->sendStatus(400); + response->sendStatus(STATUS_BAD_REQ); return; } bytesToRead -= bytesBuffered; @@ -275,7 +276,7 @@ void Server::_readRequest(std::shared_ptr socket) { "application/x-www-form-urlencoded") && !request->_parseBody()) { LOGLS_ERROR(_log, logger(self) << logger(socket) << "form decode failed"); - response->sendStatus(400); + response->sendStatus(STATUS_BAD_REQ); return; } self->_dispatchRequest(request, response); @@ -305,23 +306,23 @@ void Server::_dispatchRequest(Request::Ptr request, Response::Ptr response) { << "exception thrown from handler: " << e.what()); switch (e.code().value()) { case errc::permission_denied: - response->sendStatus(403); + response->sendStatus(STATUS_FORBIDDEN); break; default: - response->sendStatus(500); + response->sendStatus(STATUS_INTERNAL_SERVER_ERR); break; } } catch (std::exception const& e) { LOGLS_ERROR(_log, logger(this) << logger(request->_socket) << "exception thrown from handler: " << e.what()); - response->sendStatus(500); + response->sendStatus(STATUS_INTERNAL_SERVER_ERR); } return; } } } LOGLS_DEBUG(_log, logger(this) << logger(request->_socket) << "no handler found"); - response->sendStatus(404); + response->sendStatus(STATUS_NOT_FOUND); } } // namespace lsst::qserv::qhttp diff --git a/src/qhttp/StaticContent.cc b/src/qhttp/StaticContent.cc index 1848d4e966..3f8813ee88 100644 --- a/src/qhttp/StaticContent.cc +++ b/src/qhttp/StaticContent.cc @@ -30,6 +30,7 @@ // Local headers #include "lsst/log/Log.h" #include "qhttp/LogHelpers.h" +#include "qhttp/Status.h" namespace errc = boost::system::errc; namespace fs = boost::filesystem; @@ -63,7 +64,7 @@ void StaticContent::add(Server& server, std::string const& pattern, std::string requestPath /= request->params["0"]; requestPath = fs::weakly_canonical(requestPath); if (!boost::starts_with(requestPath, rootPath)) { - response->sendStatus(403); + response->sendStatus(STATUS_FORBIDDEN); return; } @@ -73,15 +74,15 @@ void StaticContent::add(Server& server, std::string const& pattern, std::string if (fs::is_directory(requestPath)) { if (!boost::ends_with(request->path, "/")) { response->headers["Location"] = request->path + "/"; - response->sendStatus(301); + response->sendStatus(STATUS_MOVED_PERM); return; } requestPath /= "index.html"; } - // Handle the oft-expected 404 case here explicitly, rather than as an exception. + // Handle the oft-expected case here explicitly, rather than as an exception. if (!fs::exists(requestPath)) { - response->sendStatus(404); + response->sendStatus(STATUS_NOT_FOUND); return; } diff --git a/src/qhttp/Status.h b/src/qhttp/Status.h new file mode 100644 index 0000000000..77fdec7633 --- /dev/null +++ b/src/qhttp/Status.h @@ -0,0 +1,94 @@ +/* + * LSST Data Management System + * + * This product includes software developed by the + * LSST Project (http://www.lsst.org/). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the LSST License Statement and + * the GNU General Public License along with this program. If not, + * see . + */ +#ifndef LSST_QSERV_QSTATUS_H +#define LSST_QSERV_QSTATUS_H + +// This header declarations + +namespace lsst::qserv::qhttp { + +/** + * The enumeration type for symbolic definitions of the known HTTP status codes. + */ +enum Status : unsigned int { + STATUS_CONTINUE = 100, + STATUS_SWITCH_PROTOCOL = 101, + STATUS_PROCESSING = 102, + STATUS_OK = 200, + STATUS_CREATED = 201, + STATUS_ACCEPTED = 202, + STATUS_NON_AUTHORATIVE_INFO = 203, + STATUS_NO_CONTENT = 204, + STATUS_RESET_CONTENT = 205, + STATUS_PARTIAL_CONTENT = 206, + STATUS_MULTI_STATUS = 207, + STATUS_ALREADY_REPORTED = 208, + STATUS_IM_USED = 226, + STATUS_MULTIPLE_CHOICES = 300, + STATUS_MOVED_PERM = 301, + STATUS_FOUND = 302, + STATUS_SEE_OTHER = 303, + STATUS_NOT_MODIFIED = 304, + STATUS_USE_PROXY = 305, + STATUS_TEMP_REDIRECT = 307, + STATUS_PERM_REDIRECT = 308, + STATUS_BAD_REQ = 400, + STATUS_UNAUTHORIZED = 401, + STATUS_PAYMENT_REQUIRED = 402, + STATUS_FORBIDDEN = 403, + STATUS_NOT_FOUND = 404, + STATUS_METHOD_NOT_ALLOWED = 405, + STATUS_NON_ACCEPTABLE = 406, + STATUS_PROXY_AUTH_REQUIRED = 407, + STATUS_REQ_TIMEOUT = 408, + STATUS_CONFLICT = 409, + STATUS_GONE = 410, + STATUS_LENGTH_REQUIRED = 411, + STATUS_PRECOND_FAILED = 412, + STATUS_PAYLOAD_TOO_LARGE = 413, + STATUS_URI_TOO_LONG = 414, + STATUS_UNSUPPORTED_MEDIA_TYPE = 415, + STATUS_RANGE_NOT_SATISFIABLE = 416, + STATUS_FAILED_EXPECT = 417, + STATUS_MISREDIRECT_REQ = 421, + STATUS_UNPROCESSIBLE = 422, + STATUS_LOCKED = 423, + STATUS_FAILED_DEP = 424, + STATUS_UPGRADE_REQUIRED = 426, + STATUS_PRECOND_REQUIRED = 428, + STATUS_TOO_MANY_REQS = 429, + STATUS_REQ_HDR_FIELDS_TOO_LARGE = 431, + STATUS_INTERNAL_SERVER_ERR = 500, + STATUS_NOT_IMPL = 501, + STATUS_BAD_GATEWAY = 502, + STATUS_SERVICE_UNAVAIL = 503, + STATUS_GSATEWAY_TIMEOUT = 504, + STATUS_UNDSUPPORT_VERSION = 505, + STATUS_VARIANT_NEGOTIATES = 506, + STATUS_NO_STORAGE = 507, + STATUS_LOOP = 508, + STATUS_NOT_EXTENDED = 510, + STATUS_NET_AUTH_REQUIRED = 511 +}; + +} // namespace lsst::qserv::qhttp + +#endif /* LSST_QSERV_QSTATUS_H */ diff --git a/src/qhttp/testqhttp.cc b/src/qhttp/testqhttp.cc index 6f28b35f35..db3dfe56d8 100644 --- a/src/qhttp/testqhttp.cc +++ b/src/qhttp/testqhttp.cc @@ -40,6 +40,7 @@ #include "lsst/log/Log.h" #include "qhttp/Server.h" +#include "qhttp/Status.h" namespace asio = boost::asio; namespace ip = boost::asio::ip; @@ -93,7 +94,7 @@ class CurlEasy { CurlEasy& perform(); - CurlEasy& validate(int responseCode, std::string const& contentType); + CurlEasy& validate(lsst::qserv::qhttp::Status responseCode, std::string const& contentType); CURL* hcurl; curl_slist* hlist; @@ -145,7 +146,7 @@ CurlEasy& CurlEasy::perform() { return *this; } -CurlEasy& CurlEasy::validate(int responseCode, std::string const& contentType) { +CurlEasy& CurlEasy::validate(lsst::qserv::qhttp::Status responseCode, std::string const& contentType) { long recdResponseCode; char* recdContentType = nullptr; double recdContentLength; @@ -345,8 +346,9 @@ struct QhttpFixture { BOOST_FIXTURE_TEST_CASE(request_timeout, QhttpFixture) { //----- set up server with a handler on "/" and a request timeout of 20ms - server->addHandler("GET", "/", - [](qhttp::Request::Ptr req, qhttp::Response::Ptr resp) { resp->sendStatus(200); }); + server->addHandler("GET", "/", [](qhttp::Request::Ptr req, qhttp::Response::Ptr resp) { + resp->sendStatus(qhttp::STATUS_OK); + }); server->setRequestTimeout(std::chrono::milliseconds(20)); start(); @@ -388,14 +390,14 @@ BOOST_FIXTURE_TEST_CASE(shutdown, QhttpFixture) { server->addHandler("GET", "/", [&invocations](qhttp::Request::Ptr req, qhttp::Response::Ptr resp) { ++invocations; - resp->sendStatus(200); + resp->sendStatus(qhttp::STATUS_OK); }); //----- start, and verify handler invoked start(); CurlEasy curl1; - curl1.setup("GET", urlPrefix, "").perform().validate(200, "text/html"); + curl1.setup("GET", urlPrefix, "").perform().validate(qhttp::STATUS_OK, "text/html"); BOOST_TEST(invocations == 1); //----- shutdown, and verify cannot connect. Check on both existing curl object (already open @@ -411,9 +413,9 @@ BOOST_FIXTURE_TEST_CASE(shutdown, QhttpFixture) { //----- restart, and verify handler in invoked again server->start(); - curl1.setup("GET", urlPrefix, "").perform().validate(200, "text/html"); + curl1.setup("GET", urlPrefix, "").perform().validate(qhttp::STATUS_OK, "text/html"); BOOST_TEST(invocations == 2); - curl2.setup("GET", urlPrefix, "").perform().validate(200, "text/html"); + curl2.setup("GET", urlPrefix, "").perform().validate(qhttp::STATUS_OK, "text/html"); BOOST_TEST(invocations == 3); } @@ -423,9 +425,9 @@ BOOST_FIXTURE_TEST_CASE(case_insensitive_headers, QhttpFixture) { server->addHandler("GET", "/", [](qhttp::Request::Ptr req, qhttp::Response::Ptr resp) { if ((req->header["foobar"] == "baz") && (req->header["FOOBAR"] == "baz") && (req->header["FooBar"] == "baz")) { - resp->sendStatus(200); + resp->sendStatus(qhttp::STATUS_OK); } else { - resp->sendStatus(500); + resp->sendStatus(qhttp::STATUS_INTERNAL_SERVER_ERR); } }); @@ -434,8 +436,8 @@ BOOST_FIXTURE_TEST_CASE(case_insensitive_headers, QhttpFixture) { //----- tests provide same header in multiple cases - curl.setup("GET", urlPrefix, "", {"foobar: baz"}).perform().validate(200, "text/html"); - curl.setup("GET", urlPrefix, "", {"FOOBAR: baz"}).perform().validate(200, "text/html"); + curl.setup("GET", urlPrefix, "", {"foobar: baz"}).perform().validate(qhttp::STATUS_OK, "text/html"); + curl.setup("GET", urlPrefix, "", {"FOOBAR: baz"}).perform().validate(qhttp::STATUS_OK, "text/html"); } BOOST_FIXTURE_TEST_CASE(percent_decoding, QhttpFixture) { @@ -458,7 +460,7 @@ BOOST_FIXTURE_TEST_CASE(percent_decoding, QhttpFixture) { //----- send in request with percent encodes and check echoed params curl.setup("GET", urlPrefix + "path%2Dwith%2d%2F-and-%3F?key-with-%3D=value-with-%26&key2=value2", ""); - curl.perform().validate(200, "text/plain"); + curl.perform().validate(qhttp::STATUS_OK, "text/plain"); BOOST_TEST(curl.recdContent == "params[] query[key-with-==value-with-&,key2=value2]"); } @@ -477,34 +479,38 @@ BOOST_FIXTURE_TEST_CASE(static_content, QhttpFixture) { //----- test default index.html - curl.setup("GET", urlPrefix, "").perform().validate(200, "text/html"); + curl.setup("GET", urlPrefix, "").perform().validate(qhttp::STATUS_OK, "text/html"); compareWithFile(curl.recdContent, dataDir + "index.html"); //----- test subdirectories and file typing by extension - curl.setup("GET", urlPrefix + "css/style.css", "").perform().validate(200, "text/css"); + curl.setup("GET", urlPrefix + "css/style.css", "").perform().validate(qhttp::STATUS_OK, "text/css"); compareWithFile(curl.recdContent, dataDir + "css/style.css"); - curl.setup("GET", urlPrefix + "images/lsst.gif", "").perform().validate(200, "image/gif"); + curl.setup("GET", urlPrefix + "images/lsst.gif", "").perform().validate(qhttp::STATUS_OK, "image/gif"); compareWithFile(curl.recdContent, dataDir + "images/lsst.gif"); - curl.setup("GET", urlPrefix + "images/lsst.jpg", "").perform().validate(200, "image/jpeg"); + curl.setup("GET", urlPrefix + "images/lsst.jpg", "").perform().validate(qhttp::STATUS_OK, "image/jpeg"); compareWithFile(curl.recdContent, dataDir + "images/lsst.jpg"); - curl.setup("GET", urlPrefix + "images/lsst.png", "").perform().validate(200, "image/png"); + curl.setup("GET", urlPrefix + "images/lsst.png", "").perform().validate(qhttp::STATUS_OK, "image/png"); compareWithFile(curl.recdContent, dataDir + "images/lsst.png"); - curl.setup("GET", urlPrefix + "js/main.js", "").perform().validate(200, "application/javascript"); + curl.setup("GET", urlPrefix + "js/main.js", "") + .perform() + .validate(qhttp::STATUS_OK, "application/javascript"); compareWithFile(curl.recdContent, dataDir + "js/main.js"); //----- test redirect for directory w/o trailing "/" char* redirect = nullptr; - curl.setup("GET", urlPrefix + "css", "").perform().validate(301, "text/html"); - BOOST_TEST(curl.recdContent.find("301") != std::string::npos); + curl.setup("GET", urlPrefix + "css", "").perform().validate(qhttp::STATUS_MOVED_PERM, "text/html"); + BOOST_TEST(curl.recdContent.find(std::to_string(qhttp::STATUS_MOVED_PERM)) != std::string::npos); BOOST_TEST(curl_easy_getinfo(curl.hcurl, CURLINFO_REDIRECT_URL, &redirect) == CURLE_OK); BOOST_TEST(redirect == urlPrefix + "css/"); //----- test non-existent file - curl.setup("GET", urlPrefix + "doesNotExist", "").perform().validate(404, "text/html"); - BOOST_TEST(curl.recdContent.find("404") != std::string::npos); + curl.setup("GET", urlPrefix + "doesNotExist", "") + .perform() + .validate(qhttp::STATUS_NOT_FOUND, "text/html"); + BOOST_TEST(curl.recdContent.find(std::to_string(qhttp::STATUS_NOT_FOUND)) != std::string::npos); } BOOST_FIXTURE_TEST_CASE(relative_url_containment, QhttpFixture) { @@ -515,19 +521,19 @@ BOOST_FIXTURE_TEST_CASE(relative_url_containment, QhttpFixture) { //----- test path normalization - content = asioHttpGet("/css/../css/style.css", 200, "text/css"); + content = asioHttpGet("/css/../css/style.css", qhttp::STATUS_OK, "text/css"); compareWithFile(content, dataDir + "css/style.css"); - content = asioHttpGet("/css/./style.css", 200, "text/css"); + content = asioHttpGet("/css/./style.css", qhttp::STATUS_OK, "text/css"); compareWithFile(content, dataDir + "css/style.css"); - content = asioHttpGet("/././css/.././css/./../css/style.css", 200, "text/css"); + content = asioHttpGet("/././css/.././css/./../css/style.css", qhttp::STATUS_OK, "text/css"); compareWithFile(content, dataDir + "css/style.css"); //----- test relative path containment - content = asioHttpGet("/..", 403, "text/html"); - BOOST_TEST(content.find("403") != std::string::npos); - content = asioHttpGet("/css/../..", 403, "text/html"); - BOOST_TEST(content.find("403") != std::string::npos); + content = asioHttpGet("/..", qhttp::STATUS_FORBIDDEN, "text/html"); + BOOST_TEST(content.find(std::to_string(qhttp::STATUS_FORBIDDEN)) != std::string::npos); + content = asioHttpGet("/css/../..", qhttp::STATUS_FORBIDDEN, "text/html"); + BOOST_TEST(content.find(std::to_string(qhttp::STATUS_FORBIDDEN)) != std::string::npos); } BOOST_FIXTURE_TEST_CASE(exception_handling, QhttpFixture) { @@ -542,12 +548,13 @@ BOOST_FIXTURE_TEST_CASE(exception_handling, QhttpFixture) { }); server->addHandler("GET", "/throw-after-send", [](qhttp::Request::Ptr req, qhttp::Response::Ptr resp) { - resp->sendStatus(200); + resp->sendStatus(qhttp::STATUS_OK); throw std::runtime_error("test"); }); - server->addHandler("GET", "/invalid-content-length", - [](qhttp::Request::Ptr req, qhttp::Response::Ptr resp) { resp->sendStatus(200); }); + server->addHandler( + "GET", "/invalid-content-length", + [](qhttp::Request::Ptr req, qhttp::Response::Ptr resp) { resp->sendStatus(qhttp::STATUS_OK); }); start(); @@ -555,45 +562,46 @@ BOOST_FIXTURE_TEST_CASE(exception_handling, QhttpFixture) { //----- test EACCESS thrown from static file handler - curl.setup("GET", urlPrefix + "etc/shadow", "").perform().validate(403, "text/html"); - BOOST_TEST(curl.recdContent.find("403") != std::string::npos); + curl.setup("GET", urlPrefix + "etc/shadow", "").perform().validate(qhttp::STATUS_FORBIDDEN, "text/html"); + BOOST_TEST(curl.recdContent.find(std::to_string(qhttp::STATUS_FORBIDDEN)) != std::string::npos); //----- test exceptions thrown from user handler curl.setup("GET", urlPrefix + (boost::format("throw/%1%") % EACCES).str(), ""); - curl.perform().validate(403, "text/html"); - BOOST_TEST(curl.recdContent.find("403") != std::string::npos); + curl.perform().validate(qhttp::STATUS_FORBIDDEN, "text/html"); + BOOST_TEST(curl.recdContent.find(std::to_string(qhttp::STATUS_FORBIDDEN)) != std::string::npos); curl.setup("GET", urlPrefix + (boost::format("throw/%1%") % ENOENT).str(), ""); - curl.perform().validate(500, "text/html"); - BOOST_TEST(curl.recdContent.find("500") != std::string::npos); + curl.perform().validate(qhttp::STATUS_INTERNAL_SERVER_ERR, "text/html"); + BOOST_TEST(curl.recdContent.find(std::to_string(qhttp::STATUS_INTERNAL_SERVER_ERR)) != std::string::npos); curl.setup("GET", urlPrefix + "throw/make-stoi-throw-invalid-argument", ""); - curl.perform().validate(500, "text/html"); - BOOST_TEST(curl.recdContent.find("500") != std::string::npos); + curl.perform().validate(qhttp::STATUS_INTERNAL_SERVER_ERR, "text/html"); + BOOST_TEST(curl.recdContent.find(std::to_string(qhttp::STATUS_INTERNAL_SERVER_ERR)) != std::string::npos); //----- Test exception thrown in user handler after calling a request send() method. This would be a user // programming error, but we defend against it anyway. From the point of view of the HTTP client, // the response provided by the handler before the exception goes through. - curl.setup("GET", urlPrefix + "throw-after-send", "").perform().validate(200, "text/html"); - BOOST_TEST(curl.recdContent.find("200") != std::string::npos); + curl.setup("GET", urlPrefix + "throw-after-send", "").perform().validate(qhttp::STATUS_OK, "text/html"); + BOOST_TEST(curl.recdContent.find(std::to_string(qhttp::STATUS_OK)) != std::string::npos); //----- test resource path with embedded null - curl.setup("GET", urlPrefix + "etc/%00/", "").perform().validate(400, "text/html"); - BOOST_TEST(curl.recdContent.find("400") != std::string::npos); + curl.setup("GET", urlPrefix + "etc/%00/", "").perform().validate(qhttp::STATUS_BAD_REQ, "text/html"); + BOOST_TEST(curl.recdContent.find(std::to_string(qhttp::STATUS_BAD_REQ)) != std::string::npos); - content = asioHttpGet(std::string("/\0/", 3), 400, "text/html"); - BOOST_TEST(content.find("400") != std::string::npos); + content = asioHttpGet(std::string("/\0/", 3), qhttp::STATUS_BAD_REQ, "text/html"); + BOOST_TEST(content.find(std::to_string(qhttp::STATUS_BAD_REQ)) != std::string::npos); //----- test request with invalid Content-Length headers - content = asioHttpGet("/invalid-content-length", 400, "text/html", "not-an-integer"); - BOOST_TEST(content.find("400") != std::string::npos); + content = asioHttpGet("/invalid-content-length", qhttp::STATUS_BAD_REQ, "text/html", "not-an-integer"); + BOOST_TEST(content.find(std::to_string(qhttp::STATUS_BAD_REQ)) != std::string::npos); - content = asioHttpGet("/invalid-content-length", 400, "text/html", "18446744073709551616"); - BOOST_TEST(content.find("400") != std::string::npos); + content = asioHttpGet("/invalid-content-length", qhttp::STATUS_BAD_REQ, "text/html", + "18446744073709551616"); + BOOST_TEST(content.find(std::to_string(qhttp::STATUS_BAD_REQ)) != std::string::npos); } BOOST_FIXTURE_TEST_CASE(handler_dispatch, QhttpFixture) { @@ -617,38 +625,42 @@ BOOST_FIXTURE_TEST_CASE(handler_dispatch, QhttpFixture) { //----- Test basic handler dispatch by path and method - curl.setup("GET", urlPrefix + "api/v1/foos", "").perform().validate(200, "text/plain"); + curl.setup("GET", urlPrefix + "api/v1/foos", "").perform().validate(qhttp::STATUS_OK, "text/plain"); BOOST_TEST(curl.recdContent == "Handler1 params[] query[]"); - curl.setup("POST", urlPrefix + "api/v1/foos", "").perform().validate(200, "text/plain"); + curl.setup("POST", urlPrefix + "api/v1/foos", "").perform().validate(qhttp::STATUS_OK, "text/plain"); BOOST_TEST(curl.recdContent == "Handler2 params[] query[]"); - curl.setup("PUT", urlPrefix + "api/v1/bars", "").perform().validate(200, "text/plain"); + curl.setup("PUT", urlPrefix + "api/v1/bars", "").perform().validate(qhttp::STATUS_OK, "text/plain"); BOOST_TEST(curl.recdContent == "Handler3 params[] query[]"); - curl.setup("PATCH", urlPrefix + "api/v1/bars", "").perform().validate(200, "text/plain"); + curl.setup("PATCH", urlPrefix + "api/v1/bars", "").perform().validate(qhttp::STATUS_OK, "text/plain"); BOOST_TEST(curl.recdContent == "Handler4 params[] query[]"); - curl.setup("DELETE", urlPrefix + "api/v1/bars", "").perform().validate(200, "text/plain"); + curl.setup("DELETE", urlPrefix + "api/v1/bars", "").perform().validate(qhttp::STATUS_OK, "text/plain"); BOOST_TEST(curl.recdContent == "Handler5 params[] query[]"); //----- Test methods without installed handlers - curl.setup("GET", urlPrefix + "api/v1/bars", "").perform().validate(404, "text/html"); - BOOST_TEST(curl.recdContent.find("404") != std::string::npos); - curl.setup("PUT", urlPrefix + "api/v1/foos", "").perform().validate(404, "text/html"); - BOOST_TEST(curl.recdContent.find("404") != std::string::npos); + curl.setup("GET", urlPrefix + "api/v1/bars", "").perform().validate(qhttp::STATUS_NOT_FOUND, "text/html"); + BOOST_TEST(curl.recdContent.find(std::to_string(qhttp::STATUS_NOT_FOUND)) != std::string::npos); + curl.setup("PUT", urlPrefix + "api/v1/foos", "").perform().validate(qhttp::STATUS_NOT_FOUND, "text/html"); + BOOST_TEST(curl.recdContent.find(std::to_string(qhttp::STATUS_NOT_FOUND)) != std::string::npos); //----- Test URL parameters - curl.setup("GET", urlPrefix + "api/v1/foos?bar=baz", "").perform().validate(200, "text/plain"); + curl.setup("GET", urlPrefix + "api/v1/foos?bar=baz", "") + .perform() + .validate(qhttp::STATUS_OK, "text/plain"); BOOST_TEST(curl.recdContent == "Handler1 params[] query[bar=baz]"); curl.setup("GET", urlPrefix + "api/v1/foos?bar=bop&bar=baz&bip=bap", "") .perform() - .validate(200, "text/plain"); + .validate(qhttp::STATUS_OK, "text/plain"); BOOST_TEST(curl.recdContent == "Handler1 params[] query[bar=baz,bip=bap]"); //----- Test path captures - curl.setup("GET", urlPrefix + "api/v1/foos/boz", "").perform().validate(200, "text/plain"); + curl.setup("GET", urlPrefix + "api/v1/foos/boz", "").perform().validate(qhttp::STATUS_OK, "text/plain"); BOOST_TEST(curl.recdContent == "Handler6 params[foo=boz] query[]"); - curl.setup("GET", urlPrefix + "api/v1/foos/gleep/glorp", "").perform().validate(200, "text/plain"); + curl.setup("GET", urlPrefix + "api/v1/foos/gleep/glorp", "") + .perform() + .validate(qhttp::STATUS_OK, "text/plain"); BOOST_TEST(curl.recdContent == "Handler7 params[bar=glorp,foo=gleep] query[]"); } @@ -672,7 +684,7 @@ BOOST_FIXTURE_TEST_CASE(ajax, QhttpFixture) { HandlerFactory ajaxHandler = [&m, &ajaxHandler](CurlEasy& c, std::string const& r, int& n) { return [&m, &c, r, &n, &ajaxHandler]() { - c.validate(200, "application/json"); + c.validate(qhttp::STATUS_OK, "application/json"); BOOST_TEST(c.recdContent == r); c.recdContent.erase(); ++n; diff --git a/src/replica/testHttpAsyncReq.cc b/src/replica/testHttpAsyncReq.cc index 2c57fae5cd..fee65f6da7 100644 --- a/src/replica/testHttpAsyncReq.cc +++ b/src/replica/testHttpAsyncReq.cc @@ -41,6 +41,7 @@ #include "qhttp/Request.h" #include "qhttp/Response.h" #include "qhttp/Server.h" +#include "qhttp/Status.h" #include "replica/AsyncTimer.h" #include "replica/HttpAsyncReq.h" #include "replica/Mutex.h" @@ -178,7 +179,7 @@ BOOST_AUTO_TEST_CASE(HttpAsyncReq_simple) { testAbortTimer->cancel(); BOOST_CHECK(req->state() == HttpAsyncReq::State::FINISHED); BOOST_CHECK(req->errorMessage().empty()); - BOOST_CHECK_EQUAL(req->responseCode(), 200); + BOOST_CHECK_EQUAL(req->responseCode(), qhttp::STATUS_OK); BOOST_CHECK_EQUAL(req->responseHeader().at("Content-Length"), "0"); BOOST_CHECK_EQUAL(req->responseHeader().at("Content-Type"), "text/html"); BOOST_CHECK_EQUAL(req->responseBodySize(), 0U); @@ -234,7 +235,7 @@ BOOST_AUTO_TEST_CASE(HttpAsyncReq_body_limit_error) { testAbortTimer->cancel(); BOOST_CHECK(req->state() == HttpAsyncReq::State::BODY_LIMIT_ERROR); BOOST_CHECK(req->errorMessage().empty()); - BOOST_CHECK_EQUAL(req->responseCode(), 200); + BOOST_CHECK_EQUAL(req->responseCode(), qhttp::STATUS_OK); BOOST_CHECK_EQUAL(req->responseHeader().at("Content-Length"), to_string(serverResponseBodySize)); BOOST_CHECK_EQUAL(req->responseHeader().at("Content-Type"), "text/html"); @@ -271,7 +272,7 @@ BOOST_AUTO_TEST_CASE(HttpAsyncReq_expired) { httpServer.server()->addHandler("POST", "/delayed_response", [](qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp) { this_thread::sleep_for(chrono::milliseconds(2500)); - resp->sendStatus(200); + resp->sendStatus(qhttp::STATUS_OK); }); httpServer.start(); @@ -321,7 +322,7 @@ BOOST_AUTO_TEST_CASE(HttpAsyncReq_cancelled) { httpServer.server()->addHandler("DELETE", "/delayed_response_too", [](qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp) { this_thread::sleep_for(chrono::milliseconds(2000)); - resp->sendStatus(200); + resp->sendStatus(qhttp::STATUS_OK); }); httpServer.start(); @@ -366,9 +367,10 @@ BOOST_AUTO_TEST_CASE(HttpAsyncReq_cancelled_before_started) { // Set up and start the server ::HttpServer httpServer; - httpServer.server()->addHandler( - "GET", "/quick", - [](qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp) { resp->sendStatus(200); }); + httpServer.server()->addHandler("GET", "/quick", + [](qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp) { + resp->sendStatus(qhttp::STATUS_OK); + }); httpServer.start(); // Submit a request. @@ -430,7 +432,7 @@ BOOST_AUTO_TEST_CASE(HttpAsyncReq_delayed_server_start) { httpServer.server()->addHandler("GET", "/redirected_from", [](qhttp::Request::Ptr const& req, qhttp::Response::Ptr const& resp) { resp->headers["Location"] = "/redirected_to"; - resp->sendStatus(301); + resp->sendStatus(qhttp::STATUS_MOVED_PERM); }); // Request object will be created later. @@ -451,7 +453,7 @@ BOOST_AUTO_TEST_CASE(HttpAsyncReq_delayed_server_start) { testAbortTimer->cancel(); switch (req->state()) { case HttpAsyncReq::State::FINISHED: - BOOST_CHECK_EQUAL(req->responseCode(), 301); + BOOST_CHECK_EQUAL(req->responseCode(), qhttp::STATUS_MOVED_PERM); BOOST_CHECK_EQUAL(req->responseHeader().at("Location"), "/redirected_to"); break; case HttpAsyncReq::State::CANCELLED: