Skip to content

Commit

Permalink
Added symbolic definitions for known HTTP status codes in qhttp
Browse files Browse the repository at this point in the history
Also migrated clients of the module to use these definitions
instead of numeric literals ("magic" constants).
  • Loading branch information
iagaponenko committed Jul 6, 2023
1 parent 62ffa04 commit 087483e
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 150 deletions.
2 changes: 1 addition & 1 deletion src/qhttp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
124 changes: 62 additions & 62 deletions src/qhttp/Response.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<unsigned int, const std::string> 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<Status, const std::string> 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<std::string, const std::string> contentTypesByExtension = {
{".css", "text/css"}, {".gif", "image/gif"}, {".htm", "text/html"},
Expand All @@ -125,7 +125,7 @@ Response::Response(std::shared_ptr<Server> const server, std::shared_ptr<ip::tcp
_transmissionStarted.clear();
}

void Response::sendStatus(unsigned int status) {
void Response::sendStatus(Status status) {
this->status = status;
std::string statusStr = responseStringsByCode[status];
std::ostringstream entStr;
Expand Down Expand Up @@ -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");
Expand Down
7 changes: 5 additions & 2 deletions src/qhttp/Response.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
#include "boost/asio.hpp"
#include "boost/filesystem.hpp"

// Local headers
#include "qhttp/Status.h"

namespace lsst::qserv::qhttp {

class Server;
Expand All @@ -49,14 +52,14 @@ class Response : public std::enable_shared_from_this<Response> {
// 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<std::string, std::string> headers;

private:
Expand Down
15 changes: 8 additions & 7 deletions src/qhttp/Server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -229,7 +230,7 @@ void Server::_readRequest(std::shared_ptr<ip::tcp::socket> socket) {

if (!(request->_parseHeader() && request->_parseUri())) {
timer->cancel();
response->sendStatus(400);
response->sendStatus(STATUS_BAD_REQ);
return;
}

Expand All @@ -249,7 +250,7 @@ void Server::_readRequest(std::shared_ptr<ip::tcp::socket> 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;
Expand All @@ -275,7 +276,7 @@ void Server::_readRequest(std::shared_ptr<ip::tcp::socket> 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);
Expand Down Expand Up @@ -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
9 changes: 5 additions & 4 deletions src/qhttp/StaticContent.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down
94 changes: 94 additions & 0 deletions src/qhttp/Status.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.lsstcorp.org/LegalNotices/>.
*/
#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 */
Loading

0 comments on commit 087483e

Please sign in to comment.