#include "statistics.h" #include "libreichwein/file.h" #include "libreichwein/tempfile.h" #include #include #include #include #include #include #include #include #include #include using namespace std::string_literals; namespace bp = boost::process; namespace fs = std::filesystem; namespace { // Used to return errors by generating response page and HTTP status code std::string HttpStatus(std::string status, std::string message, std::function& SetResponseHeader) { SetResponseHeader("status", status); SetResponseHeader("content_type", "text/html"); return status + " " + message; } std::string statsPng(std::function& GetServerParam, std::function& SetResponseHeader) { // Create PNG statistics via Gnuplot std::string statistics{GetServerParam("statistics")}; Tempfile tempStats{".txt"}; // input data File::setFile(tempStats.GetPath(), statistics); Tempfile tempPng{".png"}; // output Tempfile tempGnuplot{".gnuplot"}; std::stringstream s; s << "set terminal png truecolor\n"; s << "set output " << tempPng.GetPath() << "\n"; // quotes added automatically for paths s << "set title \"Webserver Throughput\"\n"; s << "set xlabel \"Time\"\n"; s << "set timefmt \"%s\"\n"; s << "set format x \"%Y-%m-%d\"\n"; s << "set xdata time\n"; s << "set xtics rotate by 45 right\n"; s << "set ylabel \"Bytes\"\n"; s << "set datafile separator \",\"\n"; s << "set grid\n"; s << "set style fill solid\n"; s << "plot " << tempStats.GetPath() << " using 1:($4+$5) with boxes title \"Bytes I/O\" lt rgb \"#1132A7\"\n"; File::setFile(tempGnuplot.GetPath(), s.str()); int exit_code{system(("gnuplot "s + tempGnuplot.GetPath().generic_string()).c_str())}; if (exit_code) { std::cerr << "Error: gnuplot returned " << std::to_string(exit_code) << std::endl; return HttpStatus("500", "Statistics error"s, SetResponseHeader); } SetResponseHeader("content_type", "image/png"); return File::getFile(tempPng.GetPath()); } // returns sum over specified column uint64_t getSum(const std::string& stats, size_t column) { uint64_t result{0}; std::istringstream is{stats}; std::string line; while (std::getline(is, line) && !is.eof()) { std::vector elements; boost::algorithm::split(elements, line, [](char c){ return c == ','; }); if (column >= elements.size()) { std::cerr << "Error: No column " << column << " found." << std::endl; return 0; } try { result += stoull(elements[column]); } catch(...) { std::cerr << "Error: Stats value " << elements[column] << " malformed." << std::endl; } } return result; } } // anonymous namespace std::string statistics_plugin::name() { return "statistics"; } statistics_plugin::statistics_plugin() { //std::cout << "Plugin constructor" << std::endl; } statistics_plugin::~statistics_plugin() { //std::cout << "Plugin destructor" << std::endl; } std::string statistics_plugin::generate_page( std::function& GetServerParam, std::function& GetRequestParam, // request including body (POST...) std::function& SetResponseHeader // to be added to result string ) { try { // Request path must not contain "..". std::string rel_target{GetRequestParam("rel_target")}; size_t query_pos{rel_target.find("?")}; if (query_pos != rel_target.npos) rel_target = rel_target.substr(0, query_pos); std::string target{GetRequestParam("target")}; if (rel_target.find("..") != std::string::npos) { return HttpStatus("400", "Illegal request: "s + target, SetResponseHeader); } // Build the path to the requested file std::string doc_root{GetRequestParam("doc_root")}; fs::path path {fs::path{doc_root} / rel_target}; 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); // 301 Moved Permanently } if (path.filename() == "stats.png") return statsPng(GetServerParam, SetResponseHeader); try { SetResponseHeader("content_type", "text/html"); std::string header {"" "" "Webserver Statistics" ""}; std::string footer{"


"}; std::string result{header}; std::string statistics{GetServerParam("statistics")}; double ipv6_fraction_by_requests {double(getSum(statistics, 5)) / getSum(statistics, 1)}; double ipv6_fraction_by_bytes {double(getSum(statistics, 7) + getSum(statistics, 8)) / (getSum(statistics, 3) + getSum(statistics, 4))}; double https_fraction_by_requests {double(getSum(statistics, 9)) / getSum(statistics, 1)}; double https_fraction_by_bytes {double(getSum(statistics, 11) + getSum(statistics, 12)) / (getSum(statistics, 3) + getSum(statistics, 4))}; result += "

Webserver Statistics

"; result += "

Host uptime: "s + GetServerParam("uptime_host") + "

"; result += "

Webserver uptime: "s + GetServerParam("uptime_webserver") + "

"; result += "

IPv6 fraction by requests: "s + std::to_string(ipv6_fraction_by_requests * 100) + "%

"; result += "

IPv6 fraction by bytes: "s + std::to_string(ipv6_fraction_by_bytes * 100) + "%

"; result += "

HTTPS fraction by requests: "s + std::to_string(https_fraction_by_requests * 100) + "%

"; result += "

HTTPS fraction by bytes: "s + std::to_string(https_fraction_by_bytes * 100) + "%

"; result += "

"; result += "
"s + statistics + "
"s; result += footer; return result; } catch (const std::exception& ex) { return HttpStatus("500", "Statistics error: "s + ex.what(), SetResponseHeader); } } catch (const std::exception& ex) { return HttpStatus("500", "Unknown Error: "s + ex.what(), SetResponseHeader); } } bool statistics_plugin::has_own_authentication() { return false; }