191 lines
7.1 KiB
C++
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();
|
|
|
|
}
|
|
|
|
|