Compare commits

..

No commits in common. "7c2ac8456e01317c6d763d59121e5b33895d12e9" and "23d74e35a1ed3cce7b301960c8260f435de6edbd" have entirely different histories.

28 changed files with 0 additions and 30622 deletions

View File

@ -7,4 +7,3 @@ add_subdirectory(applications)
add_subdirectory(bmrshared)
add_subdirectory(bmrshared-freetype)
add_subdirectory(bmrshared-magic)
add_subdirectory(bmrshared-web)

View File

@ -1,2 +1 @@
add_subdirectory(http-mandelbrot)
add_subdirectory(text2image)

View File

@ -1,28 +0,0 @@
cmake_minimum_required(VERSION 3.20)
find_package(Boost 1.84.0 REQUIRED COMPONENTS headers CONFIG)
find_package(JPEG REQUIRED)
project(http-mandelbrot)
add_executable(
${PROJECT_NAME}
./src/main.cpp
)
set_property(
TARGET ${PROJECT_NAME}
PROPERTY CXX_STANDARD 20
)
target_link_libraries(
${PROJECT_NAME}
PRIVATE
bmrshared-web
Boost::headers
JPEG::JPEG
)
install(
TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 618 B

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,661 +0,0 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container img.leaflet-tile {
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
mix-blend-mode: plus-lighter;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
svg.leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
white-space: nowrap;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
border: none;
text-align: center;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
-ms-zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,57 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base target="_top">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quick Start - Leaflet</title>
<link rel="shortcut icon" type="image/x-icon" href="docs/images/favicon.ico" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<style>
html, body {
height: 100%;
margin: 0;
}
.leaflet-container {
height: 400px;
width: 600px;
max-width: 100%;
max-height: 100%;
}
</style>
</head>
<body>
<div id="map" style="width: 1500px; height: 1500px;"></div>
<script>
const map = L.map('map', {crs: L.CRS.Simple}).setView([0.0, 0.0], 1);
const tiles = L.tileLayer('/render_{x}_{y}_{z}', {
maxZoom: 1000
}).addTo(map);
function onMapClick(e) {
popup
.setLatLng(e.latlng)
.setContent(`You clicked the map at ${e.latlng.toString()}`)
.openOn(map);
}
map.on('click', onMapClick);
</script>
</body>
</html>

View File

@ -1,258 +0,0 @@
// 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/response_promise.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>
namespace
{
const std::regex regex_renderpath(R"REGEX(\/render_(\d+)_(\d+)_(\d+))REGEX");
template <typename T>
class window
{
public:
window(T x_min, T x_max, T y_min, T y_max)
: m_x_min(x_min), m_x_max(x_max), m_y_min(y_min), m_y_max(y_max)
{}
T size() const {
return width() * height();
}
T width() const {
return m_x_max - m_x_min;
}
T height() const {
return m_y_max - m_y_min;
}
T x_min() const {return m_x_min;}
T x_max() const {return m_x_max;}
T y_min() const {return m_y_min;}
T y_max() const {return m_y_max;}
private:
T m_x_min;
T m_x_max;
T m_y_min;
T m_y_max;
};
// Convert a pixel coordinate to the complex domain
std::complex<double> scale(const window<int>& scr, const window<double>& fr, const std::complex<double>& c)
{
return std::complex<double>(c.real() / (double)scr.width() * fr.width() + fr.x_min(),
c.imag() / (double)scr.height() * fr.height() + fr.y_min());
}
int escape(const std::complex<double>& c,
int iter_max,
const std::function<std::complex<double>(std::complex<double>, std::complex<double>)>& func)
{
std::complex<double> z(0);
int iter = 0;
while (abs(z) < 2.0 && iter < iter_max) {
z = func(z, c);
iter++;
}
return iter;
}
void fractal(
const window<int>& scr,
const window<double>&fract,
int max_iterations,
const std::function<std::complex<double>(std::complex<double>, std::complex<double>)>& fractal_fn,
const std::function<void(int x, int y, int num_iter)>& painter)
{
for(int y = scr.y_min(); y < scr.y_max(); ++y)
{
for(int x = scr.x_min(); x < scr.x_max(); ++x)
{
auto num_iter = escape(
scale(scr,
fract,
std::complex<double>{double(y), double(x)}
),
max_iterations,
fractal_fn);
painter(x,y,num_iter);
}
}
}
constexpr unsigned int config_max_simultanious_requests = 50;
constexpr uint64_t config_request_body_limit = (10 * 1024);
constexpr std::chrono::seconds config_request_timeout(30);
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(const address_type& addr, const request_type& req, bmrshared::web::response_promise promise) override
{
std::string_view target = req.target();
std::cout << "HTTP GET: " << target << std::endl;
if (req.method() != boost::beast::http::verb::get)
{
// Other methods are not supported for directory acces
//
boost::beast::http::response<boost::beast::http::string_body> bad_request{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());
promise.SendResponse(std::move(bad_request));
return;
}
std::smatch base_match;
std::string tmp(target);
if (std::regex_match(tmp, base_match, regex_renderpath) && base_match.size() == 4)
{
double offset_y = std::stoi(base_match[1]);
double offset_x = std::stoi(base_match[2]);
double z = std::stoi(base_match[3]);
auto renderfn = [offset_y, offset_x, z, req, promise]() mutable
{
constexpr int pixel_width = 256;
constexpr int pixel_height = 256;
constexpr int max_iterations = 100;
boost::gil::gray8_image_t image(pixel_width,pixel_height);
auto view = boost::gil::view(image);
boost::gil::fill_pixels(view, boost::gil::gray8_pixel_t(0));
auto painter = [&](int x, int y, int num_iter) -> void
{
if(x >= 0 && y >= 0 && x < pixel_width && y < pixel_height)
{
const auto iter = view.at({x, y});
boost::gil::color_convert(boost::gil::gray8_pixel_t(num_iter%256), *iter);
}
};
auto fract_fn = [](std::complex <double> z, std::complex<double> c) -> std::complex<double>
{
return z * z + c;
};
auto zoomfactor = 1.0/std::pow(2, z);
fractal(window<int>{0,pixel_width, 0,pixel_height},
window<double>{offset_x * zoomfactor, (offset_x + 1)*zoomfactor, offset_y * zoomfactor ,(offset_y + 1)*zoomfactor},
max_iterations,
fract_fn,
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>(95));
boost::beast::http::response<boost::beast::http::string_body> ok{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();
promise.SendResponse(std::move(ok));
};
m_ioc.post(renderfn);
}
else
{
m_dir.handle_request_http(addr, req, promise);
}
}
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();
});
ioc.post([&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() < 20)
{
threads.emplace_back([&ioc]{ioc.run();});
}
ioc.run();
threads.clear();
}

View File

@ -1 +0,0 @@
add_subdirectory(lib)

View File

@ -1,29 +0,0 @@
// 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.
//
#pragma once
#include "request_handler_interface.hpp"
#include <filesystem>
namespace bmrshared::web
{
class directory_request_handler : public request_handler_interface
{
public:
explicit directory_request_handler(const std::filesystem::path& document_root);
~directory_request_handler() override;
void handle_request_http(const address_type& address, const request_type& req, response_promise promise) override;
void handle_request_websocket_upgrade(const address_type& address,
const request_type& req,
boost::asio::ip::tcp::socket socket) override;
private:
std::filesystem::path m_document_root;
};
} // namespace bmrshared::web

View File

@ -1,26 +0,0 @@
// 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.
//
#pragma once
#include "server.hpp"
namespace bmrshared::web
{
class response_promise;
class request_handler_interface
{
public:
using address_type = server::address_type;
using request_type = server::request_type;
virtual ~request_handler_interface() = default;
virtual void handle_request_http(const address_type&, const request_type&, response_promise) = 0;
virtual void handle_request_websocket_upgrade(const address_type&, const request_type&, boost::asio::ip::tcp::socket) = 0;
};
} // namespace bmrshared::web

View File

@ -1,40 +0,0 @@
// 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.
//
#pragma once
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast.hpp>
#include <functional>
namespace bmrshared::web
{
class response_promise final
{
public:
using response_sender = std::function<void(boost::beast::tcp_stream& stream)>;
using callback_on_response = std::function<void(const response_sender& fnSendResponse)>;
response_promise() = delete;
explicit response_promise(callback_on_response cbOnResponse);
~response_promise();
template<typename Body, typename Fields>
void SendResponse(boost::beast::http::response<Body, Fields> response)
{
response_sender responder = [&response](boost::beast::tcp_stream& stream)
{
boost::beast::http::write(stream, response);
};
m_call_on_response(responder);
}
private:
callback_on_response m_call_on_response;
};
} // namespace bmrshared::web

View File

@ -1,45 +0,0 @@
// 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.
//
#pragma once
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/string_body.hpp>
#include <chrono>
#include <memory>
namespace bmrshared::web
{
namespace detail
{
class internal_server;
}
class response_promise;
class request_handler_interface;
class server final
{
public:
using address_type = boost::asio::ip::address;
using request_type = boost::beast::http::request<boost::beast::http::string_body>;
server(boost::asio::io_context& io_context,
const boost::asio::ip::tcp::endpoint& listen_endpoint,
std::chrono::seconds incoming_request_timeout,
uint64_t request_body_limit,
unsigned int max_simultaneous_requests,
request_handler_interface& handler);
~server();
private:
std::shared_ptr<detail::internal_server> m_internal;
};
} // namespace bmrshared::web

View File

@ -1,31 +0,0 @@
cmake_minimum_required(VERSION 3.20)
find_package(Boost 1.84.0 REQUIRED COMPONENTS headers CONFIG)
project(bmrshared-web)
add_library(
${PROJECT_NAME}
directory_request_handler.cpp
internal_server.cpp
response_promise.cpp
server.cpp
)
set_property(
TARGET ${PROJECT_NAME}
PROPERTY CXX_STANDARD 20
)
target_include_directories(
${PROJECT_NAME}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include
)
target_link_libraries(
${PROJECT_NAME}
PUBLIC
bmrshared
bmrshared-magic
Boost::headers
)

View File

@ -1,165 +0,0 @@
// 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/directory_request_handler.hpp>
#include <algorithm>
#include <array>
#include <bmrshared/magic_file_info.hpp>
#include <bmrshared/response_promise.hpp>
#include <filesystem>
#include <future>
#include <iomanip>
#include <iostream>
#include <optional>
#include <string_view>
namespace beast = boost::beast;
namespace http = beast::http;
namespace
{
struct ExtensionMimeMapping
{
std::string_view extension;
std::string_view mime;
};
bmrshared::magic_file_info mfi("magic.mgc");
} // namespace
using bmrshared::web::directory_request_handler;
directory_request_handler::directory_request_handler(const std::filesystem::path& document_root)
: m_document_root(std::filesystem::absolute(document_root))
{
}
directory_request_handler::~directory_request_handler() = default;
void directory_request_handler::handle_request_http(const address_type& address,
const request_type& req,
bmrshared::web::response_promise promise)
{
if ((req.method() != http::verb::get) && (req.method() != http::verb::head))
{
// Other methods are not supported for directory acces
//
http::response<http::string_body> bad_request{http::status::bad_request, req.version()};
bad_request.set(http::field::server, BOOST_BEAST_VERSION_STRING);
bad_request.set(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());
promise.SendResponse(std::move(bad_request));
return;
}
std::string_view target = req.target();
{
target.remove_prefix(std::min(target.find_first_not_of(R"(/\)"), target.size()));
}
std::filesystem::path requested_file = m_document_root / std::filesystem::path(target);
std::optional<std::filesystem::path> found_file;
// See if requested path is a directory, use index.html if that is the case.
if (std::filesystem::is_directory(requested_file))
{
auto indexPath = requested_file / "index.html";
if (std::filesystem::is_regular_file(indexPath))
{
found_file = indexPath;
}
}
// See if requested path is a file.
if (std::filesystem::is_regular_file(requested_file))
{
found_file = requested_file;
}
if (!found_file)
{
http::response<http::string_body> not_found{http::status::not_found, req.version()};
not_found.set(http::field::server, BOOST_BEAST_VERSION_STRING);
not_found.set(http::field::content_type, "text/plain");
not_found.prepare_payload();
not_found.body() = "File not found.";
not_found.keep_alive(req.keep_alive());
promise.SendResponse(std::move(not_found));
return;
}
beast::error_code ec;
http::file_body::value_type body;
body.open(found_file->native().c_str(), beast::file_mode::scan, ec);
if (ec)
{
http::response<http::string_body> internal{http::status::internal_server_error, req.version()};
internal.set(http::field::server, BOOST_BEAST_VERSION_STRING);
internal.set(http::field::content_type, "text/plain");
internal.prepare_payload();
internal.body() = "Internal server error.";
internal.keep_alive(req.keep_alive());
promise.SendResponse(std::move(internal));
return;
}
const auto file_size = body.size();
const auto file_last_modified_utc = std::chrono::time_point_cast<std::chrono::seconds>(std::filesystem::last_write_time(*found_file));
const std::string last_modified = std::format("{0:%a}, {0:0>2%d} {0:0>2%e} {0:0>4%Y} {0:%H}:{0:%M}:{0:%S} GMT", file_last_modified_utc);
const std::string mime_type = mfi.get_mime(*found_file);
if (req.count(http::field::if_modified_since) != 0 && (req[http::field::if_modified_since] == last_modified))
{
http::response<http::string_body> head{http::status::not_modified, req.version()};
head.set(http::field::server, BOOST_BEAST_VERSION_STRING);
head.keep_alive(req.keep_alive());
promise.SendResponse(std::move(head));
return;
}
// Okay, we have a file so let us send the correct response.
if (req.method() == http::verb::head)
{
http::response<http::string_body> head{http::status::ok, req.version()};
head.set(http::field::server, BOOST_BEAST_VERSION_STRING);
head.set(http::field::content_type, mime_type);
head.set(http::field::last_modified, last_modified);
head.set(http::field::cache_control, "public, max-age=360");
head.set(http::field::content_length, std::to_string(file_size));
head.keep_alive(req.keep_alive());
promise.SendResponse(std::move(head));
return;
}
else if (req.method() == http::verb::get)
{
http::response<http::file_body> head{std::piecewise_construct,
std::make_tuple(std::move(body)),
std::make_tuple(http::status::ok, req.version())};
head.set(http::field::server, BOOST_BEAST_VERSION_STRING);
head.set(http::field::content_type, mime_type);
head.set(http::field::last_modified, last_modified);
head.set(http::field::cache_control, "public, max-age=360");
head.set(http::field::content_length, std::to_string(file_size));
head.keep_alive(req.keep_alive());
promise.SendResponse(std::move(head));
return;
}
}
void directory_request_handler::handle_request_websocket_upgrade(const address_type&,
const request_type&,
boost::asio::ip::tcp::socket)
{
// We ignore websocket upgrades. throw away the socket to close connection.
}

View File

@ -1,223 +0,0 @@
// 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 "internal_server.h"
#include <bmrshared/request_handler_interface.hpp>
#include <algorithm>
using bmrshared::web::detail::internal_server;
internal_server::internal_server(boost::asio::io_context& io_context,
const boost::asio::ip::tcp::endpoint& listen_endpoint,
std::chrono::seconds incoming_request_timeout,
uint64_t request_body_limit,
unsigned int max_simultaneous_requests,
request_handler_interface& handler)
: m_io_context(io_context)
, m_endPoint(listen_endpoint)
, m_incoming_request_timeout(incoming_request_timeout)
, m_max_simultaneous_requests(max_simultaneous_requests)
, m_request_body_limit(request_body_limit)
, m_acceptor(io_context)
, m_handler(handler)
, m_sessions()
{
}
internal_server::~internal_server() = default;
void internal_server::run()
{
m_acceptor.open(m_endPoint.protocol());
m_acceptor.set_option(boost::asio::socket_base::reuse_address(true));
m_acceptor.bind(m_endPoint);
m_acceptor.listen(boost::asio::socket_base::max_listen_connections);
async_accept();
}
void internal_server::async_accept()
{
// Start accepting incoming connections asynchronously.
m_acceptor.async_accept(
[weak_server = weak_from_this()](boost::system::error_code ec, boost::asio::ip::tcp::socket socket)
{
auto shared_server = weak_server.lock();
if (shared_server)
{
shared_server->do_accept(ec, std::move(socket));
}
});
}
void internal_server::do_accept(boost::system::error_code ec, boost::asio::ip::tcp::socket socket)
{
if (ec)
{
// Error while accepting incoming connection. Ignore.
// TODO: something needs to be done here.
}
else
{
m_sessions.push_back(std::make_shared<http_session>(std::move(socket)));
session_process_queued();
async_accept();
}
}
void internal_server::session_process_queued()
{
// Determine how many outstanding requests we have.
std::size_t count_responding = 0;
for(const auto& session : m_sessions)
{
count_responding += session->m_outstanding_requests.size();
}
// Allow for more outstanding requests unless we are already over the maximum.
if (count_responding < m_max_simultaneous_requests)
{
for(auto& session : m_sessions)
{
if (count_responding > m_max_simultaneous_requests)
{
break;
}
else if (session && (session->m_state == session_state::queue_reading))
{
http_session& s = *session;
s.state = session_state::reading;
s.parser.emplace(); // Create empty parser object.
s.parser->body_limit(m_request_body_limit);
s.stream.expires_after(m_incoming_request_timeout);
auto handler = [weak_server = weak_from_this(), session](boost::beast::error_code ec, std::size_t size)
{
auto shared_server = weak_server.lock();
if (shared_server)
{
shared_server->session_on_read(session, ec, size);
}
};
boost::beast::http::async_read(s.stream, s.buffer, *s.parser, handler);
}
}
}
// First empty out all sessions queued for getting a response as much as we are allowed.
auto iter = m_sessions.begin();
while ((count_responding < m_max_simultaneous_requests) && (iter != m_sessions.end()))
{
iter = std::find_if(
iter,
m_sessions.end(),
[](std::shared_ptr<http_session> session)
{
return session && (session->state == session_state::queue_responding);
});
if (iter != m_sessions.end())
{
auto& session = *iter;
session->state = session_state::responding;
++count_responding;
// Regular http request. Pass to request handler.
auto finalize_response = [session, weak_server = weak_from_this()](const response_promise::response_sender& rs)
{
auto shared_server = weak_server.lock();
if (shared_server)
{
rs(session->stream);
// When we receive the response and have sent it, we proceed with reading
shared_server->session_finalize_response(session);
}
};
const auto& address = session->stream.socket().remote_endpoint().address();
m_handler.handle_request_http(address, session->parser->get(), response_promise(finalize_response));
}
}
if (count_responding < m_max_simultaneous_requests)
{
// Now we can dispatch reading.
for (const auto& session : m_sessions)
{
if (session && (session->state == session_state::queue_reading))
{
http_session& s = *session;
s.state = session_state::reading;
s.parser.emplace(); // Create empty parser object.
s.parser->body_limit(m_request_body_limit);
s.stream.expires_after(m_incoming_request_timeout);
auto handler = [weak_server = weak_from_this(), session](boost::beast::error_code ec, std::size_t size)
{
auto shared_server = weak_server.lock();
if (shared_server)
{
shared_server->session_on_read(session, ec, size);
}
};
boost::beast::http::async_read(s.stream, s.buffer, *s.parser, handler);
}
}
}
}
void internal_server::session_on_read(std::shared_ptr<http_session> session,
boost::beast::error_code ec,
std::size_t size)
{
http_session& s = *session;
if (ec == boost::beast::http::error::end_of_stream)
{
// End of stream. Shutdown and remove http session
s.stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
session_remove(session);
}
else if (ec)
{
// Any other kind of error, also shutdown and remove http_session
s.stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
session_remove(session);
}
else if (boost::beast::websocket::is_upgrade(s.parser->get()))
{
// Received a websocket upgrade. pass socket to request handler, remove session from our administration.
const auto& address = s.stream.socket().remote_endpoint().address();
m_handler.handle_request_websocket_upgrade(address, s.parser->get(), s.stream.release_socket());
session_remove(session);
}
else
{
// Just a regular http request. Queue for handling later.
s.state = session_state::queue_responding;
session_process_queued();
}
}
void internal_server::session_finalize_response(std::shared_ptr<http_session> session)
{
if(session)
{
session->state = session_state::queue_reading;
session_process_queued();
}
}
void internal_server::session_remove(std::shared_ptr<http_session> session)
{
m_sessions.erase(std::remove(m_sessions.begin(), m_sessions.end(), session), m_sessions.end());
}

View File

@ -1,74 +0,0 @@
// 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.
//
#pragma once
#include <bmrshared/response_promise.hpp>
#include <bmrshared/server.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <chrono>
#include <memory>
#include <queue>
namespace bmrshared::web {
namespace detail {
enum class session_state {
queue_reading,
reading
};
struct http_session {
explicit http_session(boost::asio::ip::tcp::socket s)
: stream(std::move(s))
{}
boost::beast::tcp_stream stream;
boost::beast::flat_buffer buffer;
boost::optional<boost::beast::http::request_parser<boost::beast::http::string_body>> parser;
session_state m_state;
std::queue<> m_outstanding_requests;
};
class internal_server final : public std::enable_shared_from_this<internal_server> {
public:
using address_type = bmrshared::web::server::address_type;
using request_type = bmrshared::web::server::request_type;
public:
internal_server(boost::asio::io_context& io_context,
const boost::asio::ip::tcp::endpoint& listen_endpoint,
std::chrono::seconds incoming_request_timeout,
uint64_t request_body_limit,
unsigned int max_simultaneous_requests,
request_handler_interface& handler);
~internal_server();
void run();
private:
void async_accept();
void do_accept(boost::system::error_code ec, boost::asio::ip::tcp::socket socket);
void session_process_queued();
void session_on_read(std::shared_ptr<http_session> session, boost::beast::error_code ec, std::size_t size);
void session_finalize_response(std::shared_ptr<http_session> session);
void session_remove(std::shared_ptr<http_session> session);
private:
boost::asio::io_context& m_io_context;
boost::asio::ip::tcp::endpoint m_endPoint;
std::chrono::seconds m_incoming_request_timeout;
unsigned int m_max_simultaneous_requests;
uint64_t m_request_body_limit;
boost::asio::ip::tcp::acceptor m_acceptor;
request_handler_interface& m_handler;
std::vector<std::shared_ptr<http_session>> m_sessions;
};
} // namespace detail
} // namespace bmrshared::web

View File

@ -1,17 +0,0 @@
// 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/response_promise.hpp>
using bmrshared::web::response_promise;
response_promise::response_promise(callback_on_response cbOnResponse)
: m_call_on_response(std::move(cbOnResponse))
{
}
response_promise::~response_promise() = default;

View File

@ -1,25 +0,0 @@
// 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 "internal_server.h"
#include <bmrshared/server.hpp>
using bmrshared::web::server;
using bmrshared::web::detail::internal_server;
server::server(boost::asio::io_context& io_context,
const boost::asio::ip::tcp::endpoint& listen_endpoint,
std::chrono::seconds incoming_request_timeout,
uint64_t request_body_limit,
unsigned int max_simultaneous_requests,
request_handler_interface& handler)
: m_internal(std::make_shared<internal_server>(io_context, listen_endpoint, incoming_request_timeout, request_body_limit, max_simultaneous_requests, handler))
{
m_internal->run();
}
server::~server() = default;