summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2023-02-12 18:57:09 +0100
committerRoland Reichwein <mail@reichwein.it>2023-02-12 18:57:09 +0100
commit5330a835484f16318b3b074f8cf8890203eb3393 (patch)
tree24eb06387206cca01da990937360ef946afacce3
parent92a14d375c8cd9dabc32ccb6dcbdf83321af535f (diff)
Convert from webserver API to FCGI
-rw-r--r--Makefile7
-rw-r--r--config.cpp4
-rw-r--r--config.h3
-rw-r--r--debian/control2
-rw-r--r--main.cpp4
-rw-r--r--weblog.cpp206
-rw-r--r--weblog.h13
7 files changed, 143 insertions, 96 deletions
diff --git a/Makefile b/Makefile
index bbe1cd0..5c94148 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,9 @@ include common.mk
PROJECTNAME=weblog
LDLIBS=\
+-lfcgi \
-lreichwein \
+-lfmt \
-lboost_context \
-lboost_coroutine \
-lboost_program_options \
@@ -18,7 +20,10 @@ LDLIBS=\
SRC=\
weblog.cpp config.cpp main.cpp
-all: $(PROJECTNAME)
+default: $(PROJECTNAME)
+
+all: default
+ webapp-runner ::1:9019 ./$(PROJECTNAME) -c $(PROJECTNAME).conf
$(PROJECTNAME): $(SRC:.cpp=.o)
$(CXX) $(LDFLAGS) $^ $(LDLIBS) $(LIBS) -o $@
diff --git a/config.cpp b/config.cpp
index 44e0d36..f38da99 100644
--- a/config.cpp
+++ b/config.cpp
@@ -16,7 +16,7 @@ namespace {
const std::string default_keywords{"blog, reichwein.it, weblog"};
}
-Config::Config(const std::string& config_filename):
+Config::Config(const std::filesystem::path& config_filename):
m_dataPath{default_datapath},
m_listenAddress{"::1"},
m_listenPort{9000},
@@ -41,7 +41,7 @@ Config::Config(const std::string& config_filename):
if (m_listenPort < 0 || m_listenPort > 65535)
throw std::runtime_error("Bad listen port: "s + std::to_string(m_listenPort));
- m_keywords = tree.get<int>("config.keywords", default_keywords);
+ m_keywords = tree.get<std::string>("config.keywords", default_keywords);
} catch (const std::exception& ex) {
std::cerr << "Error reading config file " << config_filename << ". Using defaults." << std::endl;
}
diff --git a/config.h b/config.h
index fff7f9d..2ef8a83 100644
--- a/config.h
+++ b/config.h
@@ -1,5 +1,6 @@
#pragma once
+#include <filesystem>
#include <string>
const std::string default_config_filename{"/etc/weblog.conf"};
@@ -14,7 +15,7 @@ private:
std::string m_keywords;
public:
- Config(const std::string& config_filename = default_config_filename);
+ Config(const std::filesystem::path& config_filename = default_config_filename);
std::string getDataPath() const;
std::string getListenAddress() const;
diff --git a/debian/control b/debian/control
index 7ba695e..02b8a58 100644
--- a/debian/control
+++ b/debian/control
@@ -2,7 +2,7 @@ Source: weblog
Section: web
Priority: optional
Maintainer: Roland Reichwein <mail@reichwein.it>
-Build-Depends: debhelper (>= 12), libboost-all-dev | libboost1.71-all-dev, clang | g++-9, llvm | g++-9, lld | g++-9, uglifyjs, python3-pkg-resources, htmlmin, cleancss, pkg-config, libfmt-dev, googletest, gcovr, libreichwein-dev
+Build-Depends: debhelper (>= 12), libboost-all-dev | libboost1.71-all-dev, clang | g++-9, llvm | g++-9, lld | g++-9, uglifyjs, python3-pkg-resources, htmlmin, cleancss, pkg-config, libfmt-dev, googletest, gcovr, libreichwein-dev, libfcgi-dev
Standards-Version: 4.5.0
Homepage: http://www.reichwein.it/weblog/
diff --git a/main.cpp b/main.cpp
index 84cec43..8cc79d0 100644
--- a/main.cpp
+++ b/main.cpp
@@ -2,6 +2,6 @@
int main(int argc, char* argv[])
{
- Weblog weblog;
- return weblog.run(argc, argv);
+ Weblog weblog{argc, argv};
+ return weblog.run();
}
diff --git a/weblog.cpp b/weblog.cpp
index 7842ac3..2005484 100644
--- a/weblog.cpp
+++ b/weblog.cpp
@@ -1,9 +1,12 @@
#include "weblog.h"
+#include "libreichwein/file.h"
#include "libreichwein/mime.h"
#include "libreichwein/stringhelper.h"
#include "libreichwein/url.h"
+#include <fcgiapp.h>
+
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/property_tree/ptree.hpp>
@@ -19,6 +22,7 @@
using namespace std::string_literals;
namespace fs = std::filesystem;
namespace pt = boost::property_tree;
+using namespace Reichwein;
using namespace Reichwein::Mime;
using namespace Reichwein::Stringhelper;
@@ -28,29 +32,12 @@ namespace {
const std::string article_filename{"article.data"};
// 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)
+ void HttpStatus(std::string status, std::string message, FCGX_Request& request)
{
- SetResponseHeader("status", status);
- SetResponseHeader("content_type", "text/html");
- return status + " " + message;
- }
+ FCGX_FPrintF(request.out, "Status: %s Error\r\n", status.c_str());
+ FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out);
- 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");
- }
+ FCGX_FPrintF(request.out, "%s %s", status.c_str(), message.c_str());
}
bool is_index_page(std::string& rel_target)
@@ -180,7 +167,7 @@ namespace {
// returns teaser of article in plain text
std::string shortVersion(const fs::path& path)
{
- std::string article {getFile(path / article_filename)};
+ std::string article {File::getFile(path / article_filename)};
size_t pos0 {article.find("\n\n")};
if (pos0 == article.npos)
return "";
@@ -207,25 +194,27 @@ namespace {
class HtmlPage
{
- std::function<std::string(const std::string& key)>& mGetRequestParam;
+ Config& m_config;
std::string mContents;
std::string mHeader;
const std::string mFooter;
public:
- HtmlPage(std::function<std::string(const std::string& key)>& GetRequestParam,
- std::string s = ""s)
- : mGetRequestParam(GetRequestParam)
- , mContents(s)
- , mHeader("<!DOCTYPE html><html><head>"
- "<meta charset=\"utf-8\"/>"
- "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
- "<title>" + GetRequestParam("WEBLOG_NAME") + "</title>"
- "<meta name=\"keywords\" content=\"" + GetRequestParam("WEBLOG_KEYWORDS") + "\"/>"
- "<link rel=\"shortcut icon\" href=\"" + mGetRequestParam("plugin_path") + "/favicon.ico\" type=\"image/x-icon\"/>"
- "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + mGetRequestParam("plugin_path") + "/blog.css\"/>"
- "</head><body><div class=\"page\">")
- , mFooter("<br/><br/><br/><div class=\"impressum\"><a href=\"" + mGetRequestParam("plugin_path") + "/impressum.html\">Impressum, Datenschutzerklärung</a></div></div></body></html>")
+ HtmlPage(FCGX_Request& request, Config& config,
+ std::string s = ""s):
+ m_config{config},
+ mContents(s),
+ mHeader("<!DOCTYPE html><html><head>"
+ "<meta charset=\"utf-8\"/>"
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
+ "<title>" + m_config.getName() + "</title>"
+ "<meta name=\"keywords\" content=\"" + m_config.getKeywords() + "\"/>"
+ "<link rel=\"shortcut icon\" href=\"" + FCGX_GetParam("PATH_INFO", request.envp) + "/favicon.ico\" type=\"image/x-icon\"/>"
+ "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + FCGX_GetParam("PATH_INFO", request.envp) + "/blog.css\"/>"
+ "</head><body><div class=\"page\">"),
+ mFooter("<br/><br/><br/><div class=\"impressum\"><a href=\""s +
+ FCGX_GetParam("PATH_INFO", request.envp) +
+ "/impressum.html\">Impressum, Datenschutzerklärung</a></div></div></body></html>")
{
}
@@ -241,18 +230,18 @@ namespace {
}
};
- std::string generateIndexPage(fs::path& path,
- std::function<std::string(const std::string& key)>& GetRequestParam,
- std::function<plugin_interface_setter_type>& SetResponseHeader,
- size_t page)
+ void generateIndexPage(fs::path& path,
+ FCGX_Request& request,
+ Config& config,
+ size_t page)
{
try {
if (page > std::numeric_limits<int>::max())
throw std::runtime_error("Bad page index: "s + std::to_string(page));
- HtmlPage htmlPage{GetRequestParam, "<h1>"s + GetRequestParam("WEBLOG_NAME") + "</h1>"s};
+ HtmlPage htmlPage{request, config, "<h1>"s + config.getName() + "</h1>"s};
- fs::path link{ GetRequestParam("plugin_path")};
+ fs::path link{ FCGX_GetParam("PATH_INFO", request.envp)};
auto list{getArticleList(path, page)};
if (list.empty())
@@ -277,21 +266,20 @@ namespace {
htmlPage += " <a href=\"?page="s + std::to_string(page + 1) + "\">older&gt;&gt;</a>"s;
htmlPage += "<br/>";
}
- SetResponseHeader("cache_control", "no-store");
- return htmlPage;
+ FCGX_FPrintF(request.out, "Cache-Control: no-store\r\n\r\n");
+ std::string data{htmlPage};
+ FCGX_PutStr(data.c_str(), data.size(), request.out);
} catch (const std::exception& ex) {
- return HttpStatus("500", "Reading Index page: "s + ex.what(), SetResponseHeader);
+ HttpStatus("500", "Reading Index page: "s + ex.what(), request);
}
}
- std::string generateArticlePage(fs::path& path,
- std::function<std::string(const std::string& key)>& GetRequestParam,
- std::function<plugin_interface_setter_type>& SetResponseHeader)
+ void generateArticlePage(fs::path& path, FCGX_Request& request, Config& config)
{
try {
auto metaData{getMetaData(path)};
- std::string data { getFile(path / article_filename)};
+ std::string data { File::getFile(path / article_filename)};
size_t pos {data.find("\n\n")};
if (pos == data.npos)
@@ -303,23 +291,25 @@ namespace {
if (it != metaData.end() && it->second == "text/plain")
data = verbatimText(data);
- HtmlPage htmlPage{GetRequestParam, "<h1>"s + metaData.at("Subject") + "</h1>"
+ HtmlPage htmlPage{request, config, "<h1>"s + metaData.at("Subject") + "</h1>"
"<div class=\"date\">" + metaData.at("Date") + "</div>"
"<br/><br/>"s + data + "<br/>&squ;"};
- return htmlPage;
+ data = htmlPage;
+ FCGX_PutStr(data.c_str(), data.size(), request.out);
} catch (const std::exception& ex) {
- return HttpStatus("500", "Reading Article: "s + ex.what(), SetResponseHeader);
+ return HttpStatus("500", "Reading Article: "s + ex.what(), request);
}
}
- std::string generateStaticFile(fs::path& path, std::function<plugin_interface_setter_type>& SetResponseHeader)
+ void generateStaticFile(fs::path& path, FCGX_Request& request)
{
try {
- SetResponseHeader("content_type", mime_type(path.string()));
- return getFile(path);
+ FCGX_FPrintF(request.out, "Content-Type: %s\r\n\r\n", mime_type(path.string()).c_str());
+ std::string data{File::getFile(path)};
+ FCGX_PutStr(data.c_str(), data.size(), request.out);
} catch (const std::exception& ex) {
- return HttpStatus("500", "Reading Article file: "s + ex.what(), SetResponseHeader);
+ HttpStatus("500", "Reading Article file: "s + ex.what(), request);
}
}
@@ -343,53 +333,63 @@ namespace {
return result;
}
+ void usage()
+ {
+ std::cout << "usage: weblog [-c <config-path>]" << std::endl;
+ }
+
+ fs::path getConfigPath(int argc, char* argv[])
+ {
+ if (argc == 2 && argv[1] == "-h"s) {
+ usage();
+ exit(0);
+ }
+ if (argc == 3 && argv[1] == "-c"s)
+ return argv[2];
+ return {};
+ }
+
} // anonymous namespace
-Weblog::Weblog()
+Weblog::Weblog(int argc, char* argv[]): m_config{getConfigPath(argc, argv).empty() ? Config{} : Config{getConfigPath(argc, argv)}}
{
- //std::cout << "Plugin constructor" << std::endl;
}
Weblog::~Weblog()
{
- //std::cout << "Plugin destructor" << std::endl;
}
-std::string Weblog::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
-)
+namespace {
+
+void generate_page(FCGX_Request& request, Config& config)
{
try {
// Make sure we can handle the method
- std::string method {GetRequestParam("method")};
+ std::string method {FCGX_GetParam("REQUEST_METHOD", request.envp)};
if (method != "GET" && method != "HEAD")
- return HttpStatus("400", "Unknown HTTP method", SetResponseHeader);
+ return HttpStatus("400", "Unknown HTTP method", request);
// Request path must not contain "..".
- std::string rel_target{GetRequestParam("rel_target")};
- std::string target{GetRequestParam("target")};
+ std::string rel_target{FCGX_GetParam("PATH_INFO", request.envp)};
+ std::string target{FCGX_GetParam("SCRIPT_NAME", request.envp)};
if (rel_target.find("..") != std::string::npos) {
- return HttpStatus("400", "Illegal request: "s + target, SetResponseHeader);
+ return HttpStatus("400", "Illegal request: "s + target, request);
}
std::unordered_map<std::string, std::string> query { SplitQueryString(rel_target) };
// Build the path to the requested file
- std::string doc_root{GetRequestParam("doc_root")};
+ std::string path_translated{FCGX_GetParam("PATH_TRANSLATED", request.envp)};
if (rel_target.size() >= 4 && std::all_of(rel_target.begin(), rel_target.begin() + 4, isdigit)) {
rel_target = rel_target.substr(0, 4) + "/" + rel_target;
}
- fs::path path {fs::path{doc_root} / rel_target};
+ fs::path path {path_translated};
if (target.size() && target.back() != '/' && fs::is_directory(path)) {
- std::string location{GetRequestParam("location") + "/"s};
- SetResponseHeader("location", location);
- return HttpStatus("301", "Correcting directory path", SetResponseHeader);
+ std::string location{FCGX_GetParam("REQUEST_URI", request.envp) + "/"s};
+ FCGX_FPrintF(request.out, "Location: %s\r\n", location.c_str());
+ return HttpStatus("301", "Correcting directory path", request);
}
- SetResponseHeader("content_type", "text/html");
-
size_t page {0};
auto it {query.find("page")};
if (it != query.end()) {
@@ -400,24 +400,60 @@ std::string Weblog::generate_page(
}
}
- if (is_index_page(rel_target))
- return generateIndexPage(path, GetRequestParam, SetResponseHeader, page);
+ if (is_index_page(rel_target)) {
+ FCGX_PutS("Content-Type: text/html\r\n", request.out);
+ return generateIndexPage(path, request, config, page);
+ }
- if (is_article_page(rel_target, path))
- return generateArticlePage(path, GetRequestParam, SetResponseHeader);
+ if (is_article_page(rel_target, path)) {
+ FCGX_PutS("Content-Type: text/html\r\n", request.out);
+ return generateArticlePage(path, request, config);
+ }
- if (is_index_file(rel_target, path) || is_article_file(rel_target, path))
- return generateStaticFile(path, SetResponseHeader);
+ if (is_index_file(rel_target, path) || is_article_file(rel_target, path)) {
+ FCGX_PutS("Content-Type: text/html\r\n", request.out);
+ return generateStaticFile(path, request);
+ }
- return HttpStatus("404", "Bad path specification: "s + rel_target, SetResponseHeader);
+ return HttpStatus("404", "Bad path specification: "s + rel_target, request);
} catch (const std::exception& ex) {
- return HttpStatus("500", "Unknown Error: "s + ex.what(), SetResponseHeader);
+ return HttpStatus("500", "Unknown Error: "s + ex.what(), request);
}
}
-void Weblog::run(int argc, char* argv[])
+} // namespace
+
+int Weblog::run()
{
+ int result = FCGX_Init();
+ if (result != 0) { // error on init
+ std::cerr << "Error: FCGX_Init()" << std::endl;
+ return 1;
+ }
+
+ result = FCGX_IsCGI();
+ if (result) {
+ std::cerr << "Error: No FCGI environment available" << std::endl;
+ return 1;
+ }
+
+ FCGX_Request request;
+ result = FCGX_InitRequest(&request, 0, 0);
+ if (result != 0) {
+ std::cerr << "Error: FCGX_InitRequest()" << std::endl;
+ return 1;
+ }
+
+ while (FCGX_Accept_r(&request) >= 0) {
+ try {
+ generate_page(request, m_config);
+ } catch (const std::exception& ex) {
+ FCGX_PutS("Status: 500 Internal Server Error\r\n", request.out);
+ FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out);
+ FCGX_FPrintF(request.out, "Error: %s\r\n", ex.what());
+ }
+ }
return 0;
}
diff --git a/weblog.h b/weblog.h
index 85eb5b1..25991ee 100644
--- a/weblog.h
+++ b/weblog.h
@@ -1,10 +1,15 @@
#pragma once
-class weblog
+#include "config.h"
+
+class Weblog
{
public:
- weblog();
- ~weblog();
+ Weblog(int argc, char* argv[]);
+ ~Weblog();
- void run(int argc, char* argv[]);
+ int run();
+
+private:
+ Config m_config;
};