diff options
| author | Roland Reichwein <mail@reichwein.it> | 2020-04-05 19:15:25 +0200 | 
|---|---|---|
| committer | Roland Reichwein <mail@reichwein.it> | 2020-04-05 19:15:25 +0200 | 
| commit | 917d4574153fa57ea43e7410006f58aa5b1bbb0b (patch) | |
| tree | 3d4535370f596a46740a434319e73daf7f2704ba | |
| parent | ddc02ba7a6cc92d07cf073395b2d41347a8d35fb (diff) | |
Separate out response handling
| -rw-r--r-- | Makefile | 1 | ||||
| -rw-r--r-- | http.cpp | 94 | ||||
| -rw-r--r-- | https.cpp | 94 | ||||
| -rw-r--r-- | response.cpp | 92 | ||||
| -rw-r--r-- | response.h | 40 | 
5 files changed, 165 insertions, 156 deletions
@@ -64,6 +64,7 @@ PROGSRC=\      http_debian10.cpp \      plugin.cpp \      privileges.cpp \ +    response.cpp \      server.cpp \      stringutil.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));  } @@ -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);  | 
