summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--TODO1
-rw-r--r--config.cpp89
-rw-r--r--config.h17
-rw-r--r--http.cpp68
-rw-r--r--https.cpp65
-rw-r--r--plugin.cpp24
-rw-r--r--plugin_interface.h4
-rw-r--r--plugins/static-files/static-files.cpp68
-rw-r--r--response.cpp145
-rw-r--r--response.h29
-rw-r--r--webserver.conf7
-rw-r--r--webserver.cpp2
12 files changed, 249 insertions, 270 deletions
diff --git a/TODO b/TODO
index 42ac228..a0de147 100644
--- a/TODO
+++ b/TODO
@@ -4,3 +4,4 @@ Debian 10
Speed up DocRoot, use string_view
read: The socket was closed due to a timeout
+statistics
diff --git a/config.cpp b/config.cpp
index 9468e39..78f33e9 100644
--- a/config.cpp
+++ b/config.cpp
@@ -46,14 +46,7 @@ void Config::readConfigfile(std::string filename)
Path path;
auto attrs = x.second.get_child("<xmlattr>");
path.requested = attrs.get<std::string>("requested");
- std::string type = attrs.get<std::string>("type");
- if (type == "files") {
- path.type = Files;
- } else if (type == "plugin") {
- path.type = Plugin;
- } else
- throw std::runtime_error("Unknown type: "s + type);
- for (const auto& param: x.second) {
+ for (const auto& param: x.second) { // get all sub-elements of <path>
if (param.first.size() > 0 && param.first[0] != '<') // exclude meta-elements like <xmlattr>
path.params[param.first.data()] = param.second.data();
}
@@ -163,7 +156,7 @@ void Config::dump() const
if (site.paths.size() == 0)
std::cout << " Warning: No paths configured." << std::endl;
for (const auto& path: site.paths) {
- std::cout << " Path: " << path.requested << " -> " << ((path.type == Files) ? "files" : "plugin") << std::endl;
+ std::cout << " Path: " << path.requested << std::endl;
for (const auto& param: path.params) {
std::cout << " " << param.first << ": " << param.second << std::endl;
}
@@ -190,6 +183,7 @@ std::string Config::DocRoot(const Socket& socket, const std::string& requested_h
// TODO: speed this up
std::string host{requested_host};
std::string result;
+ size_t path_len{0}; // find longest matching prefix
auto pos {host.find(':')};
if (pos != host.npos) {
@@ -201,10 +195,9 @@ std::string Config::DocRoot(const Socket& socket, const std::string& requested_h
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)) {
- const auto& root { path.params.at("target")};
- if (root.size() > result.size())
- result = root;
+ if (boost::starts_with(requested_path, path.requested) && path.requested.size() > path_len) {
+ path_len = path.requested.size();
+ result = path.params.at("target");
}
}
}
@@ -220,6 +213,7 @@ std::string Config::GetPlugin(const Socket& socket, const std::string& requested
// TODO: speed this up
std::string host{requested_host};
std::string result;
+ size_t path_len{0};
auto pos {host.find(':')};
if (pos != host.npos) {
@@ -231,10 +225,9 @@ std::string Config::GetPlugin(const Socket& socket, const std::string& requested
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)) {
- const auto& root { path.params.at("plugin")};
- if (root.size() > result.size())
- result = root;
+ if (boost::starts_with(requested_path, path.requested) && path.requested.size() > path_len) {
+ path_len = path.requested.size();
+ result = path.params.at("plugin");
}
}
}
@@ -245,6 +238,68 @@ 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};
+
+ auto pos {host.find(':')};
+ if (pos != host.npos) {
+ host = host.substr(0, pos);
+ }
+
+ 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};
+
+ auto pos {host.find(':')};
+ if (pos != host.npos) {
+ host = host.substr(0, pos);
+ }
+
+ 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.size() > 0 && result[0] == '/')
+ return result.substr(1);
+ return result;
+}
+
bool Config::PluginIsConfigured(const std::string& name) const
{
for (const auto& site: m_sites) {
diff --git a/config.h b/config.h
index 4f788d8..61ad1ba 100644
--- a/config.h
+++ b/config.h
@@ -8,17 +8,11 @@
namespace fs = std::filesystem;
-enum PathType
-{
- Files, // serve files
- Plugin // delegate to plugin
-};
-
struct Path
{
std::string requested; // the requested path
- PathType type;
- std::unordered_map<std::string, std::string> params; // what to serve, e.g. which filesystem path, or which plugin
+ // default entries: "plugin", "target"
+ std::unordered_map<std::string, std::string> params; // what to serve, e.g. which filesystem path (target), and which plugin
};
struct Site
@@ -74,10 +68,17 @@ 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;
+
// return true iff plugin "name" is mentioned in config
bool PluginIsConfigured(const std::string& name) const;
diff --git a/http.cpp b/http.cpp
index 714bcc4..18a8397 100644
--- a/http.cpp
+++ b/http.cpp
@@ -71,68 +71,20 @@ template<
class Send>
void
handle_request(
- Server& server,
+ ::Server& server,
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
- // Returns a bad request response
- auto const bad_request =
- [&req](beast::string_view why)
- {
- http::response<http::string_body> res{http::status::bad_request, req.version()};
- res.set(http::field::server, VersionString);
- res.set(http::field::content_type, "text/html");
- res.keep_alive(req.keep_alive());
- res.body() = std::string(why);
- res.prepare_payload();
- return res;
- };
-
- // Returns a not found response
- auto const not_found =
- [&req](beast::string_view target)
- {
- http::response<http::string_body> res{http::status::not_found, req.version()};
- res.set(http::field::server, VersionString);
- res.set(http::field::content_type, "text/html");
- res.keep_alive(req.keep_alive());
- res.body() = "The resource '" + std::string(target) + "' was not found.";
- res.prepare_payload();
- return res;
- };
-
- // Returns a server error response
- auto const server_error =
- [&req](beast::string_view what)
- {
- http::response<http::string_body> res{http::status::internal_server_error, req.version()};
- res.set(http::field::server, VersionString);
- res.set(http::field::content_type, "text/html");
- res.keep_alive(req.keep_alive());
- res.body() = "An error occurred: '" + std::string(what) + "'";
- res.prepare_payload();
- return res;
- };
-
- try {
- 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(extend_index_html(std::string(req.target()))));
- res.keep_alive(req.keep_alive());
- std::string res_data = generate_response(req, res, server);
- if (req.method() != http::verb::head) {
- res.body() = res_data;
- res.content_length(res_data.size());
- }
- return send(std::move(res));
- } 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()));
+ 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(extend_index_html(std::string(req.target()))));
+ res.keep_alive(req.keep_alive());
+ std::string res_data = generate_response(req, res, server);
+ if (req.method() != http::verb::head) {
+ res.body() = res_data;
+ res.prepare_payload();
}
-
+ return send(std::move(res));
}
//------------------------------------------------------------------------------
diff --git a/https.cpp b/https.cpp
index f312295..d410bb3 100644
--- a/https.cpp
+++ b/https.cpp
@@ -88,63 +88,16 @@ handle_request(
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
- // Returns a bad request response
- auto const bad_request =
- [&req](beast::string_view why)
- {
- http::response<http::string_body> res{http::status::bad_request, req.version()};
- res.set(http::field::server, VersionString);
- res.set(http::field::content_type, "text/html");
- res.keep_alive(req.keep_alive());
- res.body() = std::string(why);
- res.prepare_payload();
- return res;
- };
-
- // Returns a not found response
- auto const not_found =
- [&req](beast::string_view target)
- {
- http::response<http::string_body> res{http::status::not_found, req.version()};
- res.set(http::field::server, VersionString);
- res.set(http::field::content_type, "text/html");
- res.keep_alive(req.keep_alive());
- res.body() = "The resource '" + std::string(target) + "' was not found.";
- res.prepare_payload();
- return res;
- };
-
- // Returns a server error response
- auto const server_error =
- [&req](beast::string_view what)
- {
- http::response<http::string_body> res{http::status::internal_server_error, req.version()};
- res.set(http::field::server, VersionString);
- res.set(http::field::content_type, "text/html");
- res.keep_alive(req.keep_alive());
- res.body() = "An error occurred: '" + std::string(what) + "'";
- res.prepare_payload();
- return res;
- };
-
- try {
- 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(extend_index_html(std::string(req.target()))));
- res.keep_alive(req.keep_alive());
- std::string res_data = generate_response(req, res, server);
- if (req.method() != http::verb::head) {
- res.body() = res_data;
- res.content_length(res_data.size());
- }
- return send(std::move(res));
- } 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()));
+ 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(extend_index_html(std::string(req.target()))));
+ res.keep_alive(req.keep_alive());
+ std::string res_data = generate_response(req, res, server);
+ if (req.method() != http::verb::head) {
+ res.body() = res_data;
+ res.prepare_payload();
}
+ return send(std::move(res));
}
//------------------------------------------------------------------------------
diff --git a/plugin.cpp b/plugin.cpp
index 8e4182c..db0632d 100644
--- a/plugin.cpp
+++ b/plugin.cpp
@@ -60,13 +60,25 @@ bool PluginLoader::validate_config()
for (const auto& site: sites) {
for (const auto& path: site.paths) {
- if (path.type == Plugin) {
- std::string plugin {path.params.at("plugin")};
+ // path must contain target and plugin
+ auto it {path.params.find("target")};
+ if (it == path.params.end()) {
+ std::cout << "Path " << path.requested << " for site " << site.name << " is missing target specification." << std::endl;
+ return false;
+ }
- if (!m_plugins.contains(plugin)) {
- std::cout << "Configured plugin " << plugin << " not found" << std::endl;
- return false;
- }
+ it = path.params.find("plugin");
+ if (it == path.params.end()) {
+ std::cout << "Path " << path.requested << " for site " << site.name << " is missing plugin specification." << std::endl;
+ return false;
+ }
+
+ std::string plugin {it->second};
+
+ // check if plugin exists
+ if (!m_plugins.contains(plugin)) {
+ std::cout << "Configured plugin " << plugin << " not found" << std::endl;
+ return false;
}
}
}
diff --git a/plugin_interface.h b/plugin_interface.h
index 9fc2085..c0a95b9 100644
--- a/plugin_interface.h
+++ b/plugin_interface.h
@@ -5,8 +5,8 @@
#include <string>
#include <functional>
-typedef std::string(*plugin_interface_getter_type)(const std::string& key);
-typedef void(*plugin_interface_setter_type)(const std::string& key, const std::string& value);
+typedef std::string(plugin_interface_getter_type)(const std::string& key);
+typedef void(plugin_interface_setter_type)(const std::string& key, const std::string& value);
class BOOST_SYMBOL_VISIBLE webserver_plugin_interface {
public:
diff --git a/plugins/static-files/static-files.cpp b/plugins/static-files/static-files.cpp
index 358e239..ad85f22 100644
--- a/plugins/static-files/static-files.cpp
+++ b/plugins/static-files/static-files.cpp
@@ -1,8 +1,49 @@
#include "static-files.h"
+#include <filesystem>
+#include <fstream>
#include <iostream>
+#include <string>
using namespace std::string_literals;
+namespace fs = std::filesystem;
+
+namespace {
+
+std::string 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");
+ }
+}
+
+std::string extend_index_html(std::string path)
+{
+ if (path.size() == 0 || (path.size() && path.back() == '/'))
+ path.append("index.html");
+ return path;
+}
+
+// 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)
+{
+ SetResponseHeader("status", status);
+ SetResponseHeader("content_type", "text/html");
+ return status + " " + message;
+}
+
+}
std::string static_files_plugin::name()
{
@@ -26,9 +67,32 @@ std::string static_files_plugin::generate_page(
)
{
try {
- return "Static Files "s + GetServerParam("path"s);
+ // Make sure we can handle the method
+ std::string method {GetRequestParam("method")};
+ if (method != "GET" && method != "HEAD")
+ return HttpStatus("400", "Unknown HTTP method", SetResponseHeader);
+
+ // Request path must be absolute and not contain "..".
+ std::string rel_target{GetRequestParam("rel_target")};
+ if (rel_target.find("..") != std::string::npos) {
+ std::string target{GetRequestParam("target")};
+ return HttpStatus("400", "Illegal request: "s + target, SetResponseHeader);
+ }
+
+ // Build the path to the requested file
+ std::string doc_root{GetRequestParam("doc_root")};
+ std::string path {fs::path{doc_root} / extend_index_html(rel_target)};
+
+ try {
+ return getFile(path);
+ } catch (const std::runtime_error& ex) {
+ return HttpStatus("404", "Not found: "s + GetRequestParam("target"), SetResponseHeader);
+ } catch (const std::exception& ex) {
+ return HttpStatus("500", "Internal Server Error: "s + ex.what(), SetResponseHeader);
+ }
+
} catch (const std::exception& ex) {
- return "Error: "s + ex.what();
+ return HttpStatus("500", "Unknown Error: "s + ex.what(), SetResponseHeader);
}
}
diff --git a/response.cpp b/response.cpp
index 507b2d7..6b7ae6a 100644
--- a/response.cpp
+++ b/response.cpp
@@ -2,61 +2,11 @@
#include "file.h"
#include <functional>
+#include <iostream>
+#include <unordered_map>
using namespace std::placeholders;
-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() == '/')
@@ -68,61 +18,74 @@ namespace {
std::string GetServerParam(const std::string& key, Server& server)
{
- return "";
+ // following are the supported fields:
+ // ...
+ throw std::runtime_error("Unsupported server param: "s + key);
}
-std::string GetRequestParam(const std::string& key, http::request<http::string_body>& req)
+std::unordered_map<std::string, std::function<std::string(request_type&, Server&)>> 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 "";
+ }},
+};
+
+std::string GetRequestParam(const std::string& key, request_type& req, Server& server)
{
- return "";
+ auto it = GetRequestParamFunctions.find(key);
+ if (it != GetRequestParamFunctions.end())
+ return it->second(req, server);
+ throw std::runtime_error("Unsupported request param: "s + key);
}
-void SetResponseHeader(const std::string& key, const std::string& value)
+void SetResponseHeader(const std::string& key, const std::string& value, response_type& res)
{
+ // following are the supported fields:
+
+ if (key == "status") { // HTTP Status, e.g. "200" (OK)
+ res.result(unsigned(stoul(value)));
+ } else if (key == "server") { // Server name/version string
+ res.set(http::field::server, value);
+ } else if (key == "content_type") { // e.g. text/html
+ res.set(http::field::content_type, value);
+ } else
+ throw std::runtime_error("Unsupported response field: "s + key);
}
-}
+} // anonymous namespace
-std::string generate_response(http::request<http::string_body>& req, http::response<http::string_body>& res, Server& server)
+std::string generate_response(request_type& req, response_type& res, Server& server)
{
-#if 0
- std::string host{req["host"]}; // TODO: just use string_view
+ std::string host{req["host"]};
std::string target{req.target()};
std::string plugin_name { server.GetConfig().GetPlugin(server.GetSocket(), host, target)};
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, req))};
- auto SetResponseHeaderFunction{std::function<void(const std::string& key, const std::string& value)>(SetResponseHeader)};
+ 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)))};
return plugin->generate_page(GetServerParamFunction, GetRequestParamFunction, SetResponseHeaderFunction);
-
-#else
- // 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(server.GetConfig().DocRoot(server.GetSocket(), 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;
-#endif
}
diff --git a/response.h b/response.h
index a877944..c47a980 100644
--- a/response.h
+++ b/response.h
@@ -10,31 +10,8 @@
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);
-};
+typedef http::request<http::string_body> request_type;
+typedef http::response<http::string_body> response_type;
std::string extend_index_html(std::string path);
-std::string generate_response(http::request<http::string_body>& req, http::response<http::string_body>& res, Server& server);
+std::string generate_response(request_type& req, response_type& res, Server& server);
diff --git a/webserver.conf b/webserver.conf
index 0981c0d..a50dc6d 100644
--- a/webserver.conf
+++ b/webserver.conf
@@ -13,12 +13,12 @@
<host>lists.antcom.de</host>
<host>antcom.de</host>
<host>www.antcom.de</host>
- <path requested="/" type="files">
+ <path requested="/">
<plugin>static-files</plugin>
<target>/home/ernie/homepage/test</target>
</path>
<!--
- <path requested="/webbox" type="plugin">
+ <path requested="/webbox">
<plugin>webbox</plugin>
<target>/var/lib/webbox</target>
</path>
@@ -29,7 +29,8 @@
<site>
<name>marx</name>
<host>marx.antcom.de</host>
- <path requested="/" type="files">
+ <path requested="/">
+ <plugin>static-files</plugin>
<target>/home/ernie/homepage/test1</target>
</path>
<certpath>/home/ernie/code/webserver/cert.pem</certpath>
diff --git a/webserver.cpp b/webserver.cpp
index 71829bb..51dc6a7 100644
--- a/webserver.cpp
+++ b/webserver.cpp
@@ -38,7 +38,7 @@ int main(int argc, char* argv[])
plugin_loader.load_plugins();
if (!plugin_loader.validate_config())
- throw std::runtime_error("Couldn't find all configured plugins.");
+ throw std::runtime_error("Config validation error.");
return run_server(config, plugin_loader.get_plugins());
} catch (const std::exception& ex) {