summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2020-04-05 19:15:25 +0200
committerRoland Reichwein <mail@reichwein.it>2020-04-05 19:15:25 +0200
commit917d4574153fa57ea43e7410006f58aa5b1bbb0b (patch)
tree3d4535370f596a46740a434319e73daf7f2704ba
parentddc02ba7a6cc92d07cf073395b2d41347a8d35fb (diff)
Separate out response handling
-rw-r--r--Makefile1
-rw-r--r--http.cpp94
-rw-r--r--https.cpp94
-rw-r--r--response.cpp92
-rw-r--r--response.h40
5 files changed, 165 insertions, 156 deletions
diff --git a/Makefile b/Makefile
index bc7d147..06385de 100644
--- a/Makefile
+++ b/Makefile
@@ -64,6 +64,7 @@ PROGSRC=\
http_debian10.cpp \
plugin.cpp \
privileges.cpp \
+ response.cpp \
server.cpp \
stringutil.cpp
diff --git a/http.cpp b/http.cpp
index 2203ffe..eeff552 100644
--- a/http.cpp
+++ b/http.cpp
@@ -1,6 +1,7 @@
#include "http.h"
#include "server.h"
+#include "response.h"
#include <boost/beast/version.hpp>
#include <boost/beast/core.hpp>
@@ -61,33 +62,6 @@ mime_type(beast::string_view path)
return "application/text";
}
-// Append an HTTP rel-path to a local filesystem path.
-// The returned path is normalized for the platform.
-std::string
-path_cat(
- beast::string_view base,
- beast::string_view path)
-{
- if(base.empty())
- return std::string(path);
- std::string result(base);
-#ifdef BOOST_MSVC
- char constexpr path_separator = '\\';
- if(result.back() == path_separator)
- result.resize(result.size() - 1);
- result.append(path.data(), path.size());
- for(auto& c : result)
- if(c == '/')
- c = path_separator;
-#else
- char constexpr path_separator = '/';
- if(result.back() == path_separator)
- result.resize(result.size() - 1);
- result.append(path.data(), path.size());
-#endif
- return result;
-}
-
// This function produces an HTTP response for the given
// request. The type of the response object depends on the
// contents of the request, so the interface requires the
@@ -141,60 +115,24 @@ handle_request(
return res;
};
- // Make sure we can handle the method
- if( req.method() != http::verb::get &&
- req.method() != http::verb::head)
- return send(bad_request("Unknown HTTP-method"));
-
- // Request path must be absolute and not contain "..".
- if( req.target().empty() ||
- req.target()[0] != '/' ||
- req.target().find("..") != beast::string_view::npos)
- return send(bad_request("Illegal request-target"));
-
- // Build the path to the requested file
- std::string host{req["host"]}; // TODO: just use string_view
- std::string target{req.target()};
- std::string path = path_cat(config.DocRoot(socket, host, target), req.target());
- if(req.target().back() == '/')
- path.append("index.html");
-
- // Attempt to open the file
- beast::error_code ec;
- http::file_body::value_type body;
- body.open(path.c_str(), beast::file_mode::scan, ec);
-
- // Handle the case where the file doesn't exist
- if(ec == beast::errc::no_such_file_or_directory)
- return send(not_found(req.target()));
-
- // Handle an unknown error
- if(ec)
- return send(server_error(ec.message()));
-
- // Cache the size since we need it after the move
- auto const size = body.size();
-
- // Respond to HEAD request
- if(req.method() == http::verb::head)
- {
- http::response<http::empty_body> res{http::status::ok, req.version()};
- res.set(http::field::server, VersionString);
- res.set(http::field::content_type, mime_type(path));
- res.content_length(size);
- res.keep_alive(req.keep_alive());
- return send(std::move(res));
+ std::string res_data;
+ try {
+ res_data = generate_response(req, config, socket);
+ } catch(const bad_request_exception& ex) {
+ return send(bad_request(ex.what()));
+ } catch(const not_found_exception& ex) {
+ return send(not_found(ex.what()));
+ } catch(const server_error_exception& ex) {
+ return send(server_error(ex.what()));
}
-
- // Respond to GET request
- http::response<http::file_body> res{
- std::piecewise_construct,
- std::make_tuple(std::move(body)),
- std::make_tuple(http::status::ok, req.version())};
+
+ http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, VersionString);
- res.set(http::field::content_type, mime_type(path));
- res.content_length(size);
+ res.set(http::field::content_type, mime_type(extend_index_html(std::string(req.target()))));
+ res.content_length(res_data.size());
res.keep_alive(req.keep_alive());
+ if (req.method() != http::verb::head)
+ res.body() = res_data;
return send(std::move(res));
}
diff --git a/https.cpp b/https.cpp
index 0e45272..9be69a8 100644
--- a/https.cpp
+++ b/https.cpp
@@ -3,6 +3,7 @@
#include "config.h"
#include "file.h"
#include "server.h"
+#include "response.h"
#include <boost/beast/version.hpp>
@@ -74,33 +75,6 @@ mime_type(beast::string_view path)
return "application/text";
}
-// Append an HTTP rel-path to a local filesystem path.
-// The returned path is normalized for the platform.
-std::string
-path_cat(
- beast::string_view base,
- beast::string_view path)
-{
- if(base.empty())
- return std::string(path);
- std::string result(base);
-#ifdef BOOST_MSVC
- char constexpr path_separator = '\\';
- if(result.back() == path_separator)
- result.resize(result.size() - 1);
- result.append(path.data(), path.size());
- for(auto& c : result)
- if(c == '/')
- c = path_separator;
-#else
- char constexpr path_separator = '/';
- if(result.back() == path_separator)
- result.resize(result.size() - 1);
- result.append(path.data(), path.size());
-#endif
- return result;
-}
-
// This function produces an HTTP response for the given
// request. The type of the response object depends on the
// contents of the request, so the interface requires the
@@ -154,60 +128,24 @@ handle_request(
return res;
};
- // Make sure we can handle the method
- if( req.method() != http::verb::get &&
- req.method() != http::verb::head)
- return send(bad_request("Unknown HTTP-method"));
-
- // Request path must be absolute and not contain "..".
- if( req.target().empty() ||
- req.target()[0] != '/' ||
- req.target().find("..") != beast::string_view::npos)
- return send(bad_request("Illegal request-target"));
-
- // Build the path to the requested file
- std::string host{req["host"]}; // TODO: just use string_view
- std::string target{req.target()};
- std::string path = path_cat(config.DocRoot(socket, host, target), req.target());
- if(req.target().back() == '/')
- path.append("index.html");
-
- // Attempt to open the file
- beast::error_code ec;
- http::file_body::value_type body;
- body.open(path.c_str(), beast::file_mode::scan, ec);
-
- // Handle the case where the file doesn't exist
- if(ec == beast::errc::no_such_file_or_directory)
- return send(not_found(req.target()));
-
- // Handle an unknown error
- if(ec)
- return send(server_error(ec.message()));
-
- // Cache the size since we need it after the move
- auto const size = body.size();
-
- // Respond to HEAD request
- if(req.method() == http::verb::head)
- {
- http::response<http::empty_body> res{http::status::ok, req.version()};
- res.set(http::field::server, VersionString);
- res.set(http::field::content_type, mime_type(path));
- res.content_length(size);
- res.keep_alive(req.keep_alive());
- return send(std::move(res));
+ std::string res_data;
+ try {
+ res_data = generate_response(req, config, socket);
+ } catch(const bad_request_exception& ex) {
+ return send(bad_request(ex.what()));
+ } catch(const not_found_exception& ex) {
+ return send(not_found(ex.what()));
+ } catch(const server_error_exception& ex) {
+ return send(server_error(ex.what()));
}
-
- // Respond to GET request
- http::response<http::file_body> res{
- std::piecewise_construct,
- std::make_tuple(std::move(body)),
- std::make_tuple(http::status::ok, req.version())};
+
+ http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, VersionString);
- res.set(http::field::content_type, mime_type(path));
- res.content_length(size);
+ res.set(http::field::content_type, mime_type(extend_index_html(std::string(req.target()))));
+ res.content_length(res_data.size());
res.keep_alive(req.keep_alive());
+ if (req.method() != http::verb::head)
+ res.body() = res_data;
return send(std::move(res));
}
diff --git a/response.cpp b/response.cpp
new file mode 100644
index 0000000..78368f6
--- /dev/null
+++ b/response.cpp
@@ -0,0 +1,92 @@
+#include "response.h"
+#include "file.h"
+
+namespace {
+
+// Append an HTTP rel-path to a local filesystem path.
+// The returned path is normalized for the platform.
+std::string
+path_cat(
+ beast::string_view base,
+ beast::string_view path)
+{
+ if(base.empty())
+ return std::string(path);
+ std::string result(base);
+#ifdef BOOST_MSVC
+ char constexpr path_separator = '\\';
+ if(result.back() == path_separator)
+ result.resize(result.size() - 1);
+ result.append(path.data(), path.size());
+ for(auto& c : result)
+ if(c == '/')
+ c = path_separator;
+#else
+ char constexpr path_separator = '/';
+ if(result.back() == path_separator)
+ result.resize(result.size() - 1);
+ result.append(path.data(), path.size());
+#endif
+ return result;
+}
+
+}
+
+http_exception::http_exception(std::string message): m_message(message)
+{
+}
+
+const char* http_exception::what() const noexcept
+{
+ return m_message.data();
+}
+
+bad_request_exception::bad_request_exception(std::string message): http_exception(message)
+{
+}
+
+not_found_exception::not_found_exception(std::string message): http_exception(message)
+{
+}
+
+server_error_exception::server_error_exception(std::string message): http_exception(message)
+{
+}
+
+std::string extend_index_html(std::string path)
+{
+ if (path.size() && path.back() == '/')
+ path.append("index.html");
+ return path;
+}
+
+std::string generate_response(http::request<http::string_body>& req, const Config& config, const Socket& socket)
+{
+ // Make sure we can handle the method
+ if( req.method() != http::verb::get &&
+ req.method() != http::verb::head)
+ throw bad_request_exception("Unknown HTTP-method");
+
+ // Request path must be absolute and not contain "..".
+ if( req.target().empty() ||
+ req.target()[0] != '/' ||
+ req.target().find("..") != beast::string_view::npos)
+ throw bad_request_exception("Illegal request-target");
+
+ // Build the path to the requested file
+ std::string host{req["host"]}; // TODO: just use string_view
+ std::string target{req.target()};
+ std::string path = path_cat(config.DocRoot(socket, host, target), extend_index_html(std::string(req.target())));
+
+ std::string result;
+ try {
+ result = File::getFile(path);
+ } catch (const std::runtime_error& ex) {
+ throw not_found_exception(std::string(req.target()));
+ } catch (const std::exception& ex) {
+ throw server_error_exception(ex.what());
+ }
+
+ return result;
+}
+
diff --git a/response.h b/response.h
new file mode 100644
index 0000000..a093320
--- /dev/null
+++ b/response.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "config.h"
+
+#include <boost/beast/http.hpp>
+
+#include <exception>
+#include <string>
+
+namespace beast = boost::beast; // from <boost/beast.hpp>
+namespace http = beast::http; // from <boost/beast/http.hpp>
+
+class http_exception: public std::exception
+{
+ std::string m_message;
+public:
+ http_exception(std::string message);
+ virtual const char* what() const noexcept;
+};
+
+class bad_request_exception: public http_exception
+{
+public:
+ bad_request_exception(std::string message);
+};
+
+class not_found_exception: public http_exception
+{
+public:
+ not_found_exception(std::string message);
+};
+
+class server_error_exception: public http_exception
+{
+public:
+ server_error_exception(std::string message);
+};
+
+std::string extend_index_html(std::string path);
+std::string generate_response(http::request<http::string_body>& req, const Config& config, const Socket& socket);