diff options
| author | Roland Reichwein <mail@reichwein.it> | 2020-04-12 14:01:40 +0200 | 
|---|---|---|
| committer | Roland Reichwein <mail@reichwein.it> | 2020-04-12 14:01:40 +0200 | 
| commit | 3f778eecc705990598f1033e6245522f42e2fcb5 (patch) | |
| tree | dfa2af27ef4e6b6a299ecb014a684c272db77992 | |
| parent | 77a68fbe16246245937c5d692bb8c89dc14d7800 (diff) | |
Refactor path concept
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | config.cpp | 106 | ||||
| -rw-r--r-- | config.h | 15 | ||||
| -rw-r--r-- | plugins/webbox/Makefile | 1 | ||||
| -rw-r--r-- | plugins/webbox/file.cpp | 46 | ||||
| -rw-r--r-- | plugins/webbox/file.h | 15 | ||||
| -rw-r--r-- | plugins/webbox/webbox.cpp | 134 | ||||
| -rw-r--r-- | response.cpp | 151 | ||||
| -rw-r--r-- | server.h | 1 | 
9 files changed, 228 insertions, 243 deletions
@@ -1,7 +1,7 @@  DISTROS=debian10  VERSION=$(shell dpkg-parsechangelog --show-field Version)  PROJECTNAME=webserver -PLUGINS=static-files webbox # weblog cgi fcgi +PLUGINS=static-files #webbox # weblog cgi fcgi  CXX=clang++-10 @@ -194,11 +194,12 @@ void Config::dump() const   std::cout << "=============================================" << std::endl;  } -std::string Config::DocRoot(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const +// throws std::out_of_range if not found +const Path& Config::GetPath(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const  {   // TODO: speed this up   std::string host{requested_host}; - std::string result; + const Path* result{nullptr};   size_t path_len{0}; // find longest matching prefix   RemovePortFromHostname(host); @@ -210,44 +211,7 @@ std::string Config::DocRoot(const Socket& socket, const std::string& requested_h       for (const auto& path: site.paths) {        if (boost::starts_with(requested_path, path.requested) && path.requested.size() > path_len) {         path_len = path.requested.size(); -       try { -        result = path.params.at("target"); -       } catch (const std::out_of_range& ex) { -        std::cout << "Out of range at Config::DocRoot(): target" << std::endl; -        std::rethrow_exception(std::current_exception()); -       } -      } -     } -    } -   } -  } - } - - return result; -} - -std::string Config::GetPlugin(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const -{ - // TODO: speed this up - std::string host{requested_host}; - std::string result; - size_t path_len{0}; - - RemovePortFromHostname(host); - - for (const auto& site: m_sites) { -  if (std::find(socket.serve_sites.begin(), socket.serve_sites.end(), site.name) != socket.serve_sites.end()) { -   for (const auto& m_host: site.hosts) { -    if (m_host == host) { -     for (const auto& path: site.paths) { -      if (boost::starts_with(requested_path, path.requested) && path.requested.size() > path_len) { -       path_len = path.requested.size(); -       try { -        result = path.params.at("plugin"); -       } catch (const std::out_of_range& ex) { -        std::cout << "Out of range at Config::DocRoot(): target" << std::endl; -        std::rethrow_exception(std::current_exception()); -       } +       result = &path;        }       }      } @@ -255,70 +219,18 @@ std::string Config::GetPlugin(const Socket& socket, const std::string& requested    }   } - return result; -} - -std::string Config::GetPluginPath(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const -{ - // TODO: speed this up - std::string host{requested_host}; - std::string result; - size_t path_len{0}; - - RemovePortFromHostname(host); - - for (const auto& site: m_sites) { -  if (std::find(socket.serve_sites.begin(), socket.serve_sites.end(), site.name) != socket.serve_sites.end()) { -   for (const auto& m_host: site.hosts) { -    if (m_host == host) { -     for (const auto& path: site.paths) { -      if (boost::starts_with(requested_path, path.requested) && path.requested.size() > path_len) { -       path_len = path.requested.size(); -       result = path.requested; -      } -     } -    } -   } -  } - } - - return result; -} - -std::string Config::GetRelativePath(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const -{ - // TODO: speed this up - std::string host{requested_host}; - std::string result; - size_t path_len{0}; - - RemovePortFromHostname(host); - - for (const auto& site: m_sites) { -  if (std::find(socket.serve_sites.begin(), socket.serve_sites.end(), site.name) != socket.serve_sites.end()) { -   for (const auto& m_host: site.hosts) { -    if (m_host == host) { -     for (const auto& path: site.paths) { -      if (boost::starts_with(requested_path, path.requested) && path.requested.size() > path_len) { -       path_len = path.requested.size(); -       result = requested_path.substr(path_len); -      } -     } -    } -   } -  } - } + if (result == nullptr) +  throw std::out_of_range("Path not found for "s + requested_host + " " + requested_path); - if (result.size() > 0 && result[0] == '/') -  return result.substr(1); - return result; + return *result;  }  bool Config::PluginIsConfigured(const std::string& name) const  {   for (const auto& site: m_sites) {    for (const auto& path: site.paths) { -   if (path.params.find("plugin") != path.params.end()) +   auto it{path.params.find("plugin")}; +   if (it != path.params.end() && it->second == name)      return true;    }   } @@ -11,7 +11,7 @@ namespace fs = std::filesystem;  struct Path  {   std::string requested; // the requested path - // default entries: "plugin", "target" + // mandatory entries: "plugin", "target", others are optional   std::unordered_map<std::string, std::string> params; // what to serve, e.g. which filesystem path (target), and which plugin  }; @@ -68,17 +68,8 @@ class Config    // secondary calculation functions    // -  /// Root path in server's file system -  /// param[in] requested_host e.g. www.domain.com:8080 or www.domain.com -  std::string DocRoot(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const; - -  /// Get name of plugin -  std::string GetPlugin(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const; - -  /// requested_path = GetPluginPath() / GetRelativePath() -  std::string GetPluginPath(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const; -  std::string GetRelativePath(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const; - +  const Path& GetPath(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const; +     // return true iff plugin "name" is mentioned in config    bool PluginIsConfigured(const std::string& name) const; diff --git a/plugins/webbox/Makefile b/plugins/webbox/Makefile index ed66b50..e16171d 100644 --- a/plugins/webbox/Makefile +++ b/plugins/webbox/Makefile @@ -57,6 +57,7 @@ LIBS+= \  endif  PROGSRC=\ +    file.cpp \      webbox.cpp  TESTSRC=\ diff --git a/plugins/webbox/file.cpp b/plugins/webbox/file.cpp new file mode 100644 index 0000000..47ab8be --- /dev/null +++ b/plugins/webbox/file.cpp @@ -0,0 +1,46 @@ +#include "file.h" + +#include <fstream> + +namespace fs = std::filesystem; + +using namespace std::string_literals; + +std::string File::getFile(const fs::path& filename) +{ + std::ifstream file(filename.string(), std::ios::in | std::ios::binary | std::ios::ate); + + if (file.is_open()) { +  std::ifstream::pos_type fileSize = file.tellg(); +  file.seekg(0, std::ios::beg); + +  std::string bytes(fileSize, ' '); +  file.read(reinterpret_cast<char*>(bytes.data()), fileSize); + +  return bytes; + + } else { +  throw std::runtime_error("Opening "s + filename.string() + " for reading"); + } +} + +void File::setFile(const fs::path& filename, const std::string& s) +{ + File::setFile(filename, s.data(), s.size()); +} + +void File::setFile(const fs::path& filename, const char* data, size_t size) +{ + std::ofstream file(filename.string(), std::ios::out | std::ios::binary); + if (file.is_open()) { +  file.write(data, size); + } else { +  throw std::runtime_error("Opening "s + filename.string() + " for writing"); + } +} + +void File::setFile(const fs::path& filename, const std::vector<uint8_t>& data) +{ + File::setFile(filename, reinterpret_cast<const char*>(data.data()), data.size()); +} + diff --git a/plugins/webbox/file.h b/plugins/webbox/file.h new file mode 100644 index 0000000..e7e4cf6 --- /dev/null +++ b/plugins/webbox/file.h @@ -0,0 +1,15 @@ +#pragma once + +#include <cstdint> +#include <filesystem> +#include <string> +#include <vector> + +namespace File { + +std::string getFile(const std::filesystem::path& filename); +void setFile(const std::filesystem::path& filename, const std::string& s); +void setFile(const std::filesystem::path& filename, const char* data, size_t size); +void setFile(const std::filesystem::path& filename, const std::vector<uint8_t>& data); + +} diff --git a/plugins/webbox/webbox.cpp b/plugins/webbox/webbox.cpp index 6166895..5d3f64c 100644 --- a/plugins/webbox/webbox.cpp +++ b/plugins/webbox/webbox.cpp @@ -1,101 +1,42 @@  #include "webbox.h" +#include <boost/algorithm/string/replace.hpp> +  #include <iostream> +#include <string> +#include <unordered_map>  using namespace std::string_literals; -std::string webbox_plugin::name() -{ - return "webbox"; -} +namespace { -webbox_plugin::webbox_plugin() -{ - //std::cout << "Plugin constructor" << std::endl; -} -webbox_plugin::~webbox_plugin() -{ - //std::cout << "Plugin destructor" << std::endl; -} + unordered_map<std::string> status_map { +  { "400", "Bad Request"}, +  { "403", "Forbidden" }, +  { "404", "Not Found" }, +  { "505", "Internal Server Error" }, + }; -std::string webbox_plugin::generate_page( -  std::function<std::string(const std::string& key)>& GetServerParam, -  std::function<std::string(const std::string& key)>& GetRequestParam, // request including body (POST...) -  std::function<void(const std::string& key, const std::string& value)>& SetResponseHeader // to be added to result string -) +// Used to return errors by generating response page and HTTP status code +std::string HttpStatus(std::string status, std::string message, std::function<plugin_interface_setter_type>& SetResponseHeader)  { - return "Webbox"; -} + SetResponseHeader("status", status); + SetResponseHeader("content_type", "text/html"); -#if 0 -#include <fcgiapp.h> - -#include <QString> -#include <QStringList> -#include <QHash> -#include <QDir> -#include <QFileInfo> -#include <QXmlStreamReader> -#include <QXmlStreamWriter> -#include <QDateTime> -#include <QProcess> -#include <QTemporaryFile> -#include <QUrlQuery> -#include <QPair> - -#define BUFSIZE 1000000 - -// XML special characters: -// < : < -// > : > -// & : & -// " : " -// ' : ' -// -// here:replace & -QString escapeXML(QString s) { -	s.replace("&", "&"); -	return s; -} + auto it{status_map.find(status)}; + std::string description{"(Unknown)"}; + if (it != status_map.end()) +  description = it->second; -// revert escapeXML(); -QString unescapeXML(QString s) { -	s.replace("&", "&"); -	return s; + return "<html><body><h1>"s + status + " "s + description + "</h1><p>"s + message + "</p></body></html>";  } -// supported httpStatusCode: -// 400 Bad Request -// 403 Forbidden -// 404 Not Found -// 500 Internal Server Error -// message: additional message -QString httpError(int httpStatusCode, QString message) { -	QString description; - -	switch(httpStatusCode) { -		case 400: -			description = "Bad Request"; -			break; -		case 403: -			description = "Forbidden"; -			break; -		case 404: -			description = "Not Found"; -			break; -		case 500: -			description = "Internal Server Error"; -			break; -		default: -			message = QString("Bad error code: %1, message: %2").arg(httpStatusCode).arg(message); -			httpStatusCode = 500; -			description = "Internal Server Error"; -	} -	return QString("Status: %1 %2\r\nContent-Type: text/html\r\n\r\n<html><body><h1>%1 %2</h1><p>%3</p></body></html>\r\n").arg(httpStatusCode).arg(description).arg(message); -}  struct CommandParameters { +  std::function<std::string(const std::string& key)>& GetServerParam; +  std::function<std::string(const std::string& key)>& GetRequestParam; // request including body (POST...) +  std::function<void(const std::string& key, const std::string& value)>& SetResponseHeader; // to be added to result string  	FCGX_Request request; // the request  	QUrlQuery urlQuery; // derived from request @@ -721,7 +662,7 @@ class DownloadCommand: public GetCommand {  				FCGX_PutS("Content-Type: application/octet-stream\r\n\r\n", p.request.out);  				while (!file.atEnd()) { -					QByteArray ba = file.read(BUFSIZE); +					QByteArray ba = File::getFile();  					FCGX_PutStr(ba.data(), ba.size(), p.request.out);  				}  			} else { @@ -834,4 +775,29 @@ int main(int argc, char* argv[]) {  	return 0;  } -#endif + +} // anonymous namespace + +std::string webbox_plugin::name() +{ + return "webbox"; +} + +webbox_plugin::webbox_plugin() +{ + //std::cout << "Plugin constructor" << std::endl; +} + +webbox_plugin::~webbox_plugin() +{ + //std::cout << "Plugin destructor" << std::endl; +} + +std::string webbox_plugin::generate_page( +  std::function<std::string(const std::string& key)>& GetServerParam, +  std::function<std::string(const std::string& key)>& GetRequestParam, // request including body (POST...) +  std::function<void(const std::string& key, const std::string& value)>& SetResponseHeader // to be added to result string +) +{ + return "Webbox"; +} diff --git a/response.cpp b/response.cpp index 4d6aaaf..1ee8932 100644 --- a/response.cpp +++ b/response.cpp @@ -1,14 +1,59 @@  #include "response.h"  #include "file.h" +#include <boost/algorithm/string/predicate.hpp> +  #include <functional>  #include <iostream> +#include <string>  #include <unordered_map>  using namespace std::placeholders;  namespace { +class RequestContext +{ +private: + request_type& m_req; + std::string m_host; + std::string m_target; + Server& m_server; + const Path& m_path; + +public: + RequestContext(request_type& req, Server& server) +  : m_req(req) +  , m_host(req["host"]) +  , m_target(req.target()) +  , m_server(server) +  , m_path(server.GetConfig().GetPath(server.GetSocket(), m_host, m_target)) + { + } + + const Path& GetPath() const {return m_path;} +  + std::string GetPluginName() const {return m_path.params.at("plugin");} // can throw std::out_of_range +  + std::string GetPluginPath() const {return m_path.requested;} +  + std::string GetDocRoot() const {return m_path.params.at("target");} // can throw std::out_of_range +  + std::string GetRelativePath() const { // can throw std::runtime_error +  if (!boost::starts_with(m_target, m_path.requested)) +   throw std::runtime_error("Mismatch of target ("s + m_target + ") and plugin path(" + m_path.requested + ")"s); +  return m_target.substr(m_path.requested.size()); + } +  + std::string GetPluginParam(const std::string& key) const {return m_path.params.at(key);} // can throw std::out_of_range +  + plugin_type GetPlugin() const {return m_server.GetPlugin(m_path.params.at("plugin"));}; // can throw std::out_of_range + + request_type& GetReq() const {return m_req;} + + std::string GetTarget() const {return m_target;} +}; +  std::string extend_index_html(std::string path)  {   if (path.size() && path.back() == '/') @@ -23,39 +68,47 @@ std::string GetServerParam(const std::string& key, Server& server)   throw std::runtime_error("Unsupported server param: "s + key);  } -std::unordered_map<std::string, std::function<std::string(request_type&, Server&)>> GetRequestParamFunctions{ +std::unordered_map<std::string, std::function<std::string(RequestContext&)>> GetRequestParamFunctions{   // following are the supported fields: - {"target", [](request_type& req, Server& server){return std::string{req.target()};}}, - - {"rel_target", [](request_type& req, Server& server){ -  std::string host{req["host"]}; -  std::string target{req.target()}; -  return server.GetConfig().GetRelativePath(server.GetSocket(), host, target); - }}, - - {"doc_root", [](request_type& req, Server& server) { -  std::string host{req["host"]}; -  std::string target{req.target()}; -  return server.GetConfig().DocRoot(server.GetSocket(), host, target); - }}, - - {"method", [](request_type& req, Server& server){ -  if (req.method() == http::verb::get) -   return "GET"; -  else if (req.method() == http::verb::post) -   return "POST"; -  else if (req.method() == http::verb::head) -   return "HEAD"; -  else -   return ""; - }}, + {"target", [](RequestContext& req_ctx) {return req_ctx.GetTarget();}}, + + {"rel_target", [](RequestContext& req_ctx) {return req_ctx.GetRelativePath();}}, + + {"doc_root", [](RequestContext& req_ctx) { return req_ctx.GetDocRoot();}}, + + {"body", [](RequestContext& req_ctx) { return req_ctx.GetReq().body(); }}, + + {"method", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq().method_string()};}},  }; -std::string GetRequestParam(const std::string& key, request_type& req, Server& server) +std::string GetRequestParam(const std::string& key, RequestContext& req_ctx)  { - auto it = GetRequestParamFunctions.find(key); - if (it != GetRequestParamFunctions.end()) -  return it->second(req, server); + // first, look up functions from GetRequestParamFunctions + { +  auto it = GetRequestParamFunctions.find(key); +  if (it != GetRequestParamFunctions.end()) +   return it->second(req_ctx); + } + + // second, look up plugin parameters + { +  try { +   return req_ctx.GetPluginParam(key); +  } catch(const std::out_of_range& ex) { +   // not found +  }; + } + + // third, look up req parameters + { +  try { +   return std::string{req_ctx.GetReq()[key]}; +  } catch(...){ +   // not found +  } + } + + // otherwise: error   throw std::runtime_error("Unsupported request param: "s + key);  } @@ -129,27 +182,27 @@ response_type generate_response(request_type& req, Server& server)   res.set(http::field::content_type, mime_type(extend_index_html(std::string(req.target()))));   res.keep_alive(req.keep_alive()); - std::string host{req["host"]}; - std::string target{req.target()}; - std::string plugin_name { server.GetConfig().GetPlugin(server.GetSocket(), host, target)}; - if (plugin_name == "") { -  return HttpStatus("400", "Bad request: Host "s + host + ":"s + target + " unknown"s, res); + try { +  RequestContext req_ctx{req, server}; // can throw std::out_of_range + +  plugin_type plugin{req_ctx.GetPlugin()}; + +  auto GetServerParamFunction {std::function<std::string(const std::string& key)>(std::bind(GetServerParam, _1, std::ref(server)))}; +  auto GetRequestParamFunction {std::function<std::string(const std::string& key)>(std::bind(GetRequestParam, _1, std::ref(req_ctx)))}; +  auto SetResponseHeaderFunction{std::function<void(const std::string& key, const std::string& value)>(std::bind(SetResponseHeader, _1, _2, std::ref(res)))}; +   +  std::string res_data { plugin->generate_page(GetServerParamFunction, GetRequestParamFunction, SetResponseHeaderFunction)}; +  if (req.method() == http::verb::head) { +   res.content_length(res_data.size()); +  } else { +   res.body() = res_data; +   res.prepare_payload(); +  } + +  return res; + } catch(const std::out_of_range& ex) { +  return HttpStatus("400", "Bad request: Host "s + std::string{req["host"]} + ":"s + std::string{req.target()} + " unknown"s, res);   } - plugin_type plugin{server.GetPlugin(plugin_name)}; - - auto GetServerParamFunction {std::function<std::string(const std::string& key)>(std::bind(GetServerParam, _1, std::ref(server)))}; - auto GetRequestParamFunction {std::function<std::string(const std::string& key)>(std::bind(GetRequestParam, _1, std::ref(req), std::ref(server)))}; - auto SetResponseHeaderFunction{std::function<void(const std::string& key, const std::string& value)>(std::bind(SetResponseHeader, _1, _2, std::ref(res)))}; -  - std::string res_data { plugin->generate_page(GetServerParamFunction, GetRequestParamFunction, SetResponseHeaderFunction)}; - if (req.method() == http::verb::head) { -  res.content_length(res_data.size()); - } else { -  res.body() = res_data; -  res.prepare_payload(); - } - - return res;  } @@ -9,6 +9,7 @@ using namespace std::string_literals;  static const std::string VersionString{ "Webserver "s + std::string{VERSION} }; +// Base class for HTTP and HTTPS classes  class Server  {  protected:  | 
