network-experiment/applications/http-mandelbrot/src/main.cpp

191 lines
7.1 KiB
C++

// GNU Lesser General Public License v3.0
// Copyright (c) 2025 Bart Beumer <bart@4beumer.nl>
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License v3.0 as published by
// the Free Software Foundation.
//
#include <bmrshared/server.hpp>
#include <bmrshared/request_handler_interface.hpp>
#include <bmrshared/directory_request_handler.hpp>
#include <bmrshared/request_response.hpp>
#include <bmrshared/color.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/gil/image.hpp>
#include <boost/gil/image_view.hpp>
#include <boost/gil/extension/io/jpeg.hpp>
#include <memory>
#include <sstream>
#include <thread>
#include <vector>
#include <complex>
#include <regex>
#include "mandelbrot.hpp"
namespace
{
const std::regex regex_renderpath(R"REGEX(\/render_z(\d+)x(-?\d+)y(-?\d+)w(\d+)h(\d+)ar(-?\d+\.\d+)ai(-?\d+\.\d+)maxiter(\d+).jpg)REGEX");
constexpr unsigned int config_max_simultanious_requests = 10;
constexpr uint64_t config_request_body_limit = (10 * 1024);
constexpr std::chrono::seconds config_request_timeout(3);
const boost::asio::ip::tcp::endpoint listen_endpoint(boost::asio::ip::tcp::v4(), 9800);
class split_req : public bmrshared::web::request_handler_interface
{
public:
split_req(const std::filesystem::path& root_dir, boost::asio::io_context& ioc) :
m_dir(root_dir),
m_ioc(ioc)
{}
~split_req() override = default;
void handle_request_http(bmrshared::web::request_response rs) override
{
auto& req = rs.get_request();
std::string target = req.target();
if (req.method() != boost::beast::http::verb::get)
{
// Other methods are not supported for directory acces
//
auto& bad_request = rs.create_response<boost::beast::http::response<boost::beast::http::string_body>>(boost::beast::http::status::bad_request, req.version());
bad_request.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
bad_request.set(boost::beast::http::field::content_type, "text/plain");
bad_request.prepare_payload();
bad_request.body() = "Bad request type.\nOnly GET and HEAD are expected for this URL.";
bad_request.keep_alive(req.keep_alive());
return;
}
std::cout << "GET: " << target << std::endl;
std::smatch base_match;
std::string tmp(target);
if (std::regex_match(tmp, base_match, regex_renderpath) && base_match.size() == 9)
{
int z = std::stoi(base_match[1]);
int offset_y = std::stoi(base_match[2]);
int offset_x = std::stoi(base_match[3]);
int width = std::stoi(base_match[4]);
int height = std::stoi(base_match[5]);
double a_real = std::stof(base_match[6]);
double a_img = std::stof(base_match[7]);
int max_iter = std::stoi(base_match[8]);
std::complex<float> factor_a(a_real, a_img);
auto renderfn = [offset_y, offset_x, z, width, height, factor_a, max_iter, req, target, rs, tmp]() mutable
{
int pixel_width = width;
int pixel_height = height;
int max_iterations = max_iter;
boost::gil::rgb8_image_t image(pixel_width,pixel_height);
auto view = boost::gil::view(image);
boost::gil::fill_pixels(view, boost::gil::rgb8_pixel_t(0));
auto painter = [&](int x, int y, int num_iter) -> void
{
if(x >= 0 && y >= 0 && x < pixel_width && y < pixel_height)
{
bmrshared::color c({.h=(num_iter*2)%360,.s=1.0, .v=1.0});
auto crgb = c.rgb();
uint8_t r = crgb.r * 255;
uint8_t g = crgb.g * 255;
uint8_t b = crgb.b * 255;
const auto iter = view.at({x, y});
boost::gil::color_convert(boost::gil::rgb8_pixel_t(r,g,b), *iter);
}
};
const auto zoomfactor = 1.0/std::pow(2, z);
window<float> src_window{offset_x * zoomfactor, (offset_x + 1)*zoomfactor, offset_y * zoomfactor ,(offset_y + 1)*zoomfactor};
window<float> dst_window{0,pixel_width, 0,pixel_height};
mandelbrot<float> fractal(src_window, dst_window, max_iterations, factor_a);
fractal.apply(painter);
std::stringstream out_buffer( std::ios_base::out | std::ios_base::binary );
boost::gil::write_view(out_buffer,
boost::gil::view(image),
boost::gil::image_write_info<boost::gil::jpeg_tag>(90));
auto& ok = rs.create_response<boost::beast::http::response<boost::beast::http::string_body>>(boost::beast::http::status::ok, req.version());
ok.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
ok.set(boost::beast::http::field::content_type, "image/jpeg");
ok.body() = out_buffer.str();
ok.keep_alive(req.keep_alive());
ok.prepare_payload();
};
boost::asio::post(m_ioc, renderfn);
}
else
{
m_dir.handle_request_http(rs);
}
}
void handle_request_websocket_upgrade(const address_type& addr, const request_type& req, boost::asio::ip::tcp::socket socket) override
{
m_dir.handle_request_websocket_upgrade(addr, req, std::move(socket));
; }
private:
bmrshared::web::directory_request_handler m_dir;
boost::asio::io_context& m_ioc;
};
}
int main(int argc, char **argv)
{
std::filesystem::path root_dir = "/workspaces/network-experiment/applications/http-mandelbrot/html";
boost::asio::io_context ioc;
split_req request_handler(root_dir, ioc);
boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
std::shared_ptr<bmrshared::web::server> httpserver;
signals.async_wait([&ioc](const boost::system::error_code& error, int signal_number)
{
ioc.stop();
});
boost::asio::post(ioc, [&ioc, &httpserver, &request_handler]{
httpserver = std::make_shared<bmrshared::web::server>(
ioc,
listen_endpoint,
config_request_timeout,
config_request_body_limit,
config_max_simultanious_requests,
request_handler);
});
std::vector<std::jthread> threads;
while(threads.size() < 4)
{
threads.emplace_back([&ioc]{ioc.run();});
}
ioc.run();
threads.clear();
}