Compare commits
No commits in common. "WIP-01" and "master" have entirely different histories.
|
|
@ -0,0 +1,22 @@
|
|||
FROM alpine:3.21.3
|
||||
|
||||
# Install tools required for building.
|
||||
RUN apk update && \
|
||||
apk add --no-cache \
|
||||
autoconf \
|
||||
bash \
|
||||
build-base \
|
||||
cmake \
|
||||
clang \
|
||||
clang-extra-tools \
|
||||
gdb \
|
||||
git \
|
||||
libstdc++ \
|
||||
libtool \
|
||||
linux-headers \
|
||||
m4 \
|
||||
perl \
|
||||
python3 \
|
||||
py3-pip && \
|
||||
pip install --break-system-packages conan && \
|
||||
conan profile detect
|
||||
|
|
@ -1,28 +1,18 @@
|
|||
{
|
||||
"name": "4beumer.nl C++",
|
||||
"name": "C++",
|
||||
"build": {
|
||||
"dockerfile": "../devcontainer.Dockerfile",
|
||||
"args": {
|
||||
"BUILD_TYPE": "Debug"
|
||||
}
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"postCreateCommand": "${containerWorkspaceFolder}/.devcontainer/postcreate.py --build_type=Debug",
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"conan-extension.installArgs": [
|
||||
"-of build",
|
||||
"-s build_type=Debug"
|
||||
],
|
||||
},
|
||||
"settings": {},
|
||||
"extensions": [
|
||||
"ms-vscode.cpptools",
|
||||
"ms-python.python",
|
||||
"twxs.cmake",
|
||||
"ms-vscode.cmake-tools",
|
||||
"konicy.conan-extension"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#! /usr/bin/python
|
||||
import argparse
|
||||
import os
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--build_type')
|
||||
args = parser.parse_args()
|
||||
|
||||
print ('Executing post-create steps. Build type ', args.build_type)
|
||||
os.system('conan install /workspaces/network-experiment -of /workspaces/build -s build_type=' + args.build_type)
|
||||
|
|
@ -7,4 +7,3 @@ add_subdirectory(applications)
|
|||
add_subdirectory(bmrshared)
|
||||
add_subdirectory(bmrshared-freetype)
|
||||
add_subdirectory(bmrshared-magic)
|
||||
add_subdirectory(bmrshared-web)
|
||||
|
|
|
|||
|
|
@ -1,2 +1 @@
|
|||
add_subdirectory(http-mandelbrot)
|
||||
add_subdirectory(text2image)
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
find_package(Boost 1.89.0 REQUIRED COMPONENTS headers CONFIG)
|
||||
find_package(JPEG REQUIRED)
|
||||
|
||||
project(http-mandelbrot)
|
||||
|
||||
add_executable(
|
||||
${PROJECT_NAME}
|
||||
./src/main.cpp
|
||||
./src/mandelbrot.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
|
|
@ -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
|
|
@ -1,60 +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: 100%; height: 100%;">
|
||||
<div class="leaflet-bottom leaflet-left">
|
||||
<input type="button" id="Btn1" value="Btn1" onclick="" class="btnStyle span3" />
|
||||
<input type="button" id="Btn2" value="Btn2" onclick="SaveRoutes()" class="btnStyle span3 leaflet-control" />
|
||||
<input type="button" id="Btn3" value="Btn3" onclick="editRoutes()" class="btnStyle span3 leaflet-control" />
|
||||
<span id="studentsCount" class="lblStyle span3 leaflet-control"> Ikke rutesat: </span>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
const map = L.map('map', {crs: L.CRS.Simple, zoomSnap: 0}).setView([0.0, 0.0], 1);
|
||||
|
||||
const tiles = L.tileLayer('/render_z{z}x{x}y{y}w256h256ar8.0ai0.0maxiter200.jpg', {
|
||||
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>
|
||||
|
|
@ -1,190 +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/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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,8 +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 "mandelbrot.hpp"
|
||||
|
|
@ -1,71 +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 <complex>
|
||||
#include <functional>
|
||||
#include "window.hpp"
|
||||
|
||||
template<typename TFloat>
|
||||
class mandelbrot
|
||||
{
|
||||
public:
|
||||
using painter_fn = std::function<void(int x, int y, int iterations)>;
|
||||
using escape_fn = std::function<std::complex<TFloat>(std::complex<TFloat>, std::complex<TFloat>)>;
|
||||
|
||||
mandelbrot(
|
||||
window<TFloat> source_window,
|
||||
window<TFloat> destination_window,
|
||||
int max_iterations,
|
||||
std::complex<TFloat> factor_a)
|
||||
: m_source_window(source_window)
|
||||
, m_destination_window(destination_window)
|
||||
, m_max_iterations(max_iterations)
|
||||
, m_factor_a(factor_a)
|
||||
{}
|
||||
|
||||
void apply(const painter_fn& painter) const
|
||||
{
|
||||
auto fract_fn = [factor = m_factor_a](std::complex <TFloat> z, std::complex<TFloat> c) -> std::complex<TFloat>
|
||||
{
|
||||
return std::pow<TFloat>(z, factor) + c;
|
||||
};
|
||||
|
||||
const TFloat dst_factor_x = 1.0 / m_destination_window.width() * m_source_window.width();
|
||||
const TFloat dst_factor_y = 1.0 / m_destination_window.height() * m_source_window.height();
|
||||
|
||||
for (int x = 0; x < m_destination_window.width(); ++x)
|
||||
{
|
||||
for (int y = 0; y < m_destination_window.height(); ++y)
|
||||
{
|
||||
const auto _x = x * dst_factor_x + m_source_window.x_min();
|
||||
const auto _y = y * dst_factor_y + m_source_window.y_min();
|
||||
const int iterations = escape(std::complex<TFloat>(_y, _x), fract_fn);
|
||||
|
||||
painter(y,x,iterations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int escape(const std::complex<TFloat>& c,
|
||||
const escape_fn& func) const
|
||||
{
|
||||
std::complex<TFloat> z(0);
|
||||
int iter = 0;
|
||||
|
||||
while (abs(z) < 2.0 && iter < m_max_iterations) {
|
||||
z = func(z, c);
|
||||
iter++;
|
||||
}
|
||||
return iter;
|
||||
}
|
||||
|
||||
private:
|
||||
window<TFloat> m_source_window;
|
||||
window<TFloat> m_destination_window;
|
||||
int m_max_iterations;
|
||||
std::complex<TFloat> m_factor_a;
|
||||
};
|
||||
|
|
@ -1,41 +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
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
find_package(Boost 1.89.0 REQUIRED COMPONENTS program_options headers CONFIG)
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
find_package(Boost 1.84.0 REQUIRED COMPONENTS program_options headers CONFIG)
|
||||
find_package(JPEG REQUIRED)
|
||||
|
||||
project(text2image)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
find_package(freetype REQUIRED)
|
||||
find_package(Boost 1.89.0 REQUIRED COMPONENTS headers CONFIG)
|
||||
find_package(Boost 1.84.0 REQUIRED COMPONENTS headers CONFIG)
|
||||
|
||||
project(bmrshared-freetype)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
find_package(libmagic 5.45 REQUIRED)
|
||||
|
||||
project(bmrshared-magic)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
find_package(GTest REQUIRED)
|
||||
include(GoogleTest)
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
add_subdirectory(lib)
|
||||
|
|
@ -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(request_response rs) 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
|
||||
|
|
@ -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"
|
||||
#include <bmrshared/request_response.hpp>
|
||||
|
||||
namespace bmrshared::web
|
||||
{
|
||||
|
||||
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(request_response rs) = 0;
|
||||
virtual void handle_request_websocket_upgrade(const address_type&, const request_type&, boost::asio::ip::tcp::socket) = 0;
|
||||
};
|
||||
} // namespace bmrshared::web
|
||||
|
|
@ -1,104 +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"
|
||||
#include <boost/beast.hpp>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace bmrshared::web
|
||||
{
|
||||
class request_response final
|
||||
{
|
||||
public:
|
||||
using response_sender = std::function<void(boost::beast::tcp_stream& stream)>;
|
||||
using request_type = bmrshared::web::server::request_type;
|
||||
using callback_on_finalize = std::function<void(response_sender send_response)>;
|
||||
|
||||
private:
|
||||
class response_writer_interface
|
||||
{
|
||||
public:
|
||||
virtual ~response_writer_interface();
|
||||
virtual void write_response(boost::beast::tcp_stream&) = 0;
|
||||
};
|
||||
|
||||
template<typename response_type>
|
||||
class response_writer final : public response_writer_interface
|
||||
{
|
||||
public:
|
||||
template<typename... TArgs>
|
||||
response_writer(TArgs&&... args)
|
||||
: m_response(std::forward<TArgs>(args)...)
|
||||
{}
|
||||
|
||||
~response_writer() override = default;
|
||||
|
||||
void write_response(boost::beast::tcp_stream& stream) override
|
||||
{
|
||||
boost::beast::http::write(stream, m_response);
|
||||
}
|
||||
|
||||
response_type& response()
|
||||
{
|
||||
return m_response;
|
||||
}
|
||||
|
||||
private:
|
||||
response_type m_response;
|
||||
};
|
||||
|
||||
class internal_request_response final
|
||||
{
|
||||
public:
|
||||
// Disabling copying and moving.
|
||||
internal_request_response() = delete;
|
||||
internal_request_response(const internal_request_response&) = delete;
|
||||
internal_request_response(internal_request_response&&) = delete;
|
||||
internal_request_response& operator=(const internal_request_response&) = delete;
|
||||
internal_request_response& operator=(internal_request_response&&) = delete;
|
||||
|
||||
|
||||
internal_request_response(request_type request, callback_on_finalize cb_finalize);
|
||||
~internal_request_response();
|
||||
|
||||
const request_type& get_request() const;
|
||||
void set_response_sender(std::unique_ptr<response_writer_interface> response_writer);
|
||||
|
||||
private:
|
||||
request_type m_request;
|
||||
callback_on_finalize m_cb_finalize;
|
||||
std::unique_ptr<response_writer_interface> m_response_writer;
|
||||
};
|
||||
|
||||
public:
|
||||
request_response() = delete;
|
||||
request_response(const request_response&) = default;
|
||||
request_response(request_response&&) = default;
|
||||
request_response& operator=(const request_response&) = default;
|
||||
request_response& operator=(request_response&&) = default;
|
||||
|
||||
request_response(request_type request, callback_on_finalize cb_finalize);
|
||||
~request_response();
|
||||
|
||||
template<typename response_type, typename... arg_types>
|
||||
response_type& create_response(arg_types&&... args)
|
||||
{
|
||||
auto created = std::make_unique<response_writer<response_type>>(std::forward<arg_types>(args)...);
|
||||
auto& resp = created->response();
|
||||
m_internal->set_response_sender(std::move(created));
|
||||
return resp;
|
||||
}
|
||||
|
||||
const request_type& get_request() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<internal_request_response> m_internal;
|
||||
};
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
find_package(Boost 1.89.0 REQUIRED COMPONENTS headers CONFIG)
|
||||
project(bmrshared-web)
|
||||
|
||||
add_library(
|
||||
${PROJECT_NAME}
|
||||
directory_request_handler.cpp
|
||||
internal_server.cpp
|
||||
request_response.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
|
||||
)
|
||||
|
||||
|
|
@ -1,159 +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 <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(request_response rs)
|
||||
{
|
||||
const auto& req = rs.get_request();
|
||||
if ((req.method() != http::verb::get) && (req.method() != http::verb::head))
|
||||
{
|
||||
// Other methods are not supported for directory acces
|
||||
//
|
||||
auto& bad_request = rs.create_response<http::response<http::string_body>>(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());
|
||||
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)
|
||||
{
|
||||
auto& not_found = rs.create_response<http::response<http::string_body>>(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());
|
||||
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)
|
||||
{
|
||||
auto& internal = rs.create_response<http::response<http::string_body>>(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());
|
||||
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))
|
||||
{
|
||||
auto& head = rs.create_response<http::response<http::string_body>>(http::status::not_modified, req.version());
|
||||
head.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
head.keep_alive(req.keep_alive());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Okay, we have a file so let us send the correct response.
|
||||
if (req.method() == http::verb::head)
|
||||
{
|
||||
auto& head = rs.create_response<http::response<http::string_body>>(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());
|
||||
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());
|
||||
rs.create_response<http::response<http::file_body>>(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.
|
||||
}
|
||||
|
|
@ -1,226 +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>
|
||||
#include <iostream>
|
||||
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_strand(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)
|
||||
{
|
||||
boost::asio::post(shared_server->m_strand, [weak_server, ec, s = std::make_shared<boost::asio::ip::tcp::socket>(std::move(socket))]
|
||||
{
|
||||
auto shared_server = weak_server.lock();
|
||||
if (shared_server)
|
||||
{
|
||||
shared_server->do_accept(ec, std::move(*s));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
auto sessionptr = std::make_shared<http_session>(std::move(socket));
|
||||
m_sessions.push_back(sessionptr);
|
||||
}
|
||||
async_accept();
|
||||
session_process_queued();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (session->m_state == session_state::wait_response)
|
||||
{
|
||||
count_responding++;
|
||||
}
|
||||
}
|
||||
|
||||
// Send out all processed responses.
|
||||
for (auto& session : m_sessions)
|
||||
{
|
||||
if (session)
|
||||
{
|
||||
http_session& s = *session;
|
||||
auto& our = s.m_outstanding_request;
|
||||
|
||||
if (our && our->m_response_sender)
|
||||
{
|
||||
(*our->m_response_sender)(s.stream);
|
||||
our.reset();
|
||||
s.m_state = session_state::queue_reading;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (!session)
|
||||
{
|
||||
// do nothing.
|
||||
}
|
||||
else if (session->m_state == session_state::queue_reading)
|
||||
{
|
||||
http_session& s = *session;
|
||||
s.m_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)
|
||||
{
|
||||
boost::asio::post(shared_server->m_strand, [weak_server, session, ec, 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)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
std::shared_ptr<http_request> req = std::make_shared<http_request>();
|
||||
s.m_outstanding_request = req;
|
||||
|
||||
// A regular HTTP request.
|
||||
auto finalize_response = [session, weak_server = weak_from_this(), req](const request_response::response_sender& rs)
|
||||
{
|
||||
// We have a response, add it to our spot in the queue.
|
||||
auto shared_server = weak_server.lock();
|
||||
if (shared_server)
|
||||
{
|
||||
// Queue processing of the queue so we can continue.
|
||||
boost::asio::post(shared_server->m_strand,
|
||||
[shared_server, req, rs]
|
||||
{
|
||||
req->m_response_sender = rs;
|
||||
shared_server->session_process_queued();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const auto& address = session->stream.socket().remote_endpoint().address();
|
||||
const auto& request = session->parser->get();
|
||||
|
||||
auto rs = request_response(request, finalize_response);
|
||||
auto& internal = rs.create_response<boost::beast::http::response<boost::beast::http::string_body>>(boost::beast::http::status::internal_server_error, request.version());
|
||||
internal.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
internal.set(boost::beast::http::field::content_type, "text/plain");
|
||||
internal.prepare_payload();
|
||||
internal.body() = "Internal server error.";
|
||||
internal.keep_alive(request.keep_alive());
|
||||
|
||||
m_handler.handle_request_http(rs);
|
||||
|
||||
s.m_state = session_state::wait_response;
|
||||
}
|
||||
session_process_queued();
|
||||
}
|
||||
|
||||
void internal_server::session_finalize_response(std::shared_ptr<http_session> session)
|
||||
{
|
||||
if(session)
|
||||
{
|
||||
session->m_state = session_state::queue_reading;
|
||||
session_process_queued();
|
||||
}
|
||||
}
|
||||
|
||||
void internal_server::session_remove(std::shared_ptr<http_session> session)
|
||||
{
|
||||
m_sessions.erase(std::remove_if(m_sessions.begin(),
|
||||
m_sessions.end(),
|
||||
[session](const std::shared_ptr<http_session>& s)
|
||||
{
|
||||
return session.get() == s.get();
|
||||
}),
|
||||
m_sessions.end());
|
||||
}
|
||||
|
|
@ -1,85 +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/server.hpp>
|
||||
#include <bmrshared/request_response.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <iostream>
|
||||
|
||||
namespace bmrshared::web {
|
||||
namespace detail {
|
||||
enum class session_state {
|
||||
queue_reading,
|
||||
reading,
|
||||
wait_response
|
||||
};
|
||||
|
||||
struct http_request
|
||||
{
|
||||
std::optional<request_response::response_sender> m_response_sender;
|
||||
};
|
||||
|
||||
struct http_session {
|
||||
explicit http_session(boost::asio::ip::tcp::socket s)
|
||||
: stream(std::move(s))
|
||||
{
|
||||
}
|
||||
|
||||
~http_session()
|
||||
{
|
||||
}
|
||||
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 = session_state::queue_reading;
|
||||
std::shared_ptr<http_request> m_outstanding_request;
|
||||
};
|
||||
|
||||
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::io_context::strand m_strand;
|
||||
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
|
||||
|
|
@ -1,56 +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/request_response.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using bmrshared::web::request_response;
|
||||
|
||||
request_response::response_writer_interface::~response_writer_interface() = default;
|
||||
|
||||
request_response::internal_request_response::internal_request_response(request_type request, callback_on_finalize cb_finalize)
|
||||
: m_request(std::move(request))
|
||||
, m_cb_finalize(std::move(cb_finalize))
|
||||
, m_response_writer()
|
||||
{
|
||||
}
|
||||
|
||||
request_response::internal_request_response::~internal_request_response()
|
||||
{
|
||||
if (m_response_writer)
|
||||
{
|
||||
std::shared_ptr<response_writer_interface> shared_writer(std::move(m_response_writer));
|
||||
m_cb_finalize(
|
||||
[shared_writer](boost::beast::tcp_stream& stream)
|
||||
{
|
||||
shared_writer->write_response(stream);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const request_response::request_type& request_response::internal_request_response::get_request() const
|
||||
{
|
||||
return m_request;
|
||||
}
|
||||
|
||||
void request_response::internal_request_response::set_response_sender(std::unique_ptr<response_writer_interface> response_writer)
|
||||
{
|
||||
m_response_writer = std::move(response_writer);
|
||||
}
|
||||
|
||||
request_response::request_response(request_type request, callback_on_finalize cb_finalize)
|
||||
: m_internal(std::make_unique<internal_request_response>(std::move(request), std::move(cb_finalize)))
|
||||
{
|
||||
}
|
||||
|
||||
request_response::~request_response() = default;
|
||||
|
||||
const request_response::request_type& request_response::get_request() const
|
||||
{
|
||||
return m_internal->get_request();
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
// GNU Lesser General Public License v3.0
|
||||
// Copyright (c) 2023 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 <cmath>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace bmrshared
|
||||
{
|
||||
class color
|
||||
{
|
||||
public:
|
||||
struct color_hsv
|
||||
{
|
||||
double h;
|
||||
double s;
|
||||
double v;
|
||||
};
|
||||
|
||||
struct color_rgb
|
||||
{
|
||||
double r;
|
||||
double g;
|
||||
double b;
|
||||
};
|
||||
|
||||
color() = delete;
|
||||
~color() = default;
|
||||
constexpr color(const color&) = default;
|
||||
constexpr color(color&&) = default;
|
||||
constexpr color& operator=(const color&) = default;
|
||||
constexpr color& operator=(color&&) = default;
|
||||
|
||||
constexpr color(const color_hsv& c)
|
||||
: m_rgb()
|
||||
, m_hsv(c)
|
||||
{}
|
||||
|
||||
constexpr color(const color_rgb& c)
|
||||
: m_rgb(c)
|
||||
, m_hsv()
|
||||
{}
|
||||
|
||||
constexpr const color_rgb& rgb() const
|
||||
{
|
||||
if (!m_rgb)
|
||||
{
|
||||
m_rgb = calculate_rgb(*m_hsv);
|
||||
}
|
||||
return *m_rgb;
|
||||
}
|
||||
|
||||
constexpr const color_hsv& hsv() const
|
||||
{
|
||||
if (!m_hsv)
|
||||
{
|
||||
m_hsv = calculate_hsv(*m_rgb);
|
||||
}
|
||||
return *m_hsv;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr color_hsv calculate_hsv(const color_rgb& c)
|
||||
{
|
||||
color_hsv r{.h = 0.0, .s = 0.0, .v = 0.0};
|
||||
double min = std::min(c.r, std::min(c.g,c.b));
|
||||
r.v = std::max(c.r, std::max(c.g,c.b));
|
||||
double delta = r.v - min;
|
||||
r.s = (r.v <= 0.0) ? 0.0 : (delta / r.v);
|
||||
if (r.s <= 0.0)
|
||||
{
|
||||
r.h = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (c.r == r.v)
|
||||
{
|
||||
r.h = (c.g - c.b) / delta;
|
||||
}
|
||||
else if (c.g == r.v)
|
||||
{
|
||||
r.h = 2.0 + (c.b - c.r) / delta;
|
||||
}
|
||||
else if (c.b == r.v)
|
||||
{
|
||||
r.h = 4.0 + (c.r - c.g) / delta;
|
||||
}
|
||||
r.h*=60.0;
|
||||
if (r.h < 0.0)
|
||||
{
|
||||
r.h +=360.0;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static constexpr color_rgb calculate_rgb(const color_hsv& in)
|
||||
{
|
||||
double C = in.s * in.v;
|
||||
double X = C*(1-std::fabs(std::fmod(in.h/60.0, 2)-1));
|
||||
double m = in.v-C;
|
||||
|
||||
color_rgb result{.r = 0.0, .g = 0.0, .b = 0.0};
|
||||
if(in.h >= 0.0 && in.h < 60.0){
|
||||
result = {.r = C, .g = X, .b = 0.0};
|
||||
}
|
||||
else if(in.h >= 60.0 && in.h < 120.0){
|
||||
result = {.r = X, .g = C, . b = 0.0};
|
||||
}
|
||||
else if(in.h >= 120.0 && in.h < 180.0){
|
||||
result = {.r = 0.0, .g = C, .b = X};
|
||||
}
|
||||
else if(in.h >= 180.0 && in.h < 240.0){
|
||||
result = {.r = 0.0, .g = X, .b = C};
|
||||
}
|
||||
else if(in.h >= 240.0 && in.h < 300.0){
|
||||
result = {.r = X, .g = 0.0, .b = C};
|
||||
}
|
||||
else{
|
||||
result = {.r = C, .g = 0.0, .b = X};
|
||||
}
|
||||
result.r+=m;
|
||||
result.g+=m;
|
||||
result.b+=m;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
mutable std::optional<color_rgb> m_rgb;
|
||||
mutable std::optional<color_hsv> m_hsv;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(bmrshared)
|
||||
|
||||
add_library(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
find_package(GTest REQUIRED)
|
||||
include(GoogleTest)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps
|
|||
|
||||
class HelloConan(ConanFile):
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
requires = "boost/1.89.0", "gtest/1.14.0", "libmagic/5.45", "freetype/2.14.1", "libjpeg/9f"
|
||||
requires = "boost/1.84.0", "gtest/1.14.0", "libmagic/5.45", "freetype/2.13.3", "libjpeg/9f"
|
||||
generators = "CMakeDeps"
|
||||
build_policy = "*"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
FROM alpine:3.21.3
|
||||
|
||||
# Build type used by conan. "Debug" and "Release" are supported.
|
||||
ARG BUILD_TYPE=Debug
|
||||
|
||||
RUN mkdir -p /root/.local/bin
|
||||
ENV PATH="${PATH}:/root/.local/bin"
|
||||
|
||||
# Install packages needed in both building and running.
|
||||
RUN apk update && \
|
||||
apk add --no-cache \
|
||||
libstdc++
|
||||
|
||||
# Install tools required for building.
|
||||
RUN apk add --no-cache \
|
||||
autoconf \
|
||||
bash \
|
||||
build-base \
|
||||
cmake \
|
||||
clang \
|
||||
clang-extra-tools \
|
||||
gdb \
|
||||
git \
|
||||
libtool \
|
||||
linux-headers \
|
||||
ninja \
|
||||
m4 \
|
||||
perl \
|
||||
python3 \
|
||||
pipx && \
|
||||
pipx ensurepath
|
||||
RUN pipx install conan && \
|
||||
conan profile detect
|
||||
|
||||
# Pre-build some conan packages we require and take a lot of time to build. This to
|
||||
# avoid expensive rebuilding everytime we need a fresh dev container..
|
||||
RUN mkdir -p /workspaces/tmp
|
||||
WORKDIR /workspaces/tmp
|
||||
RUN conan install -s build_type=${BUILD_TYPE} -b=* --requires=boost/1.89.0
|
||||
RUN conan install -s build_type=${BUILD_TYPE} -b=* --requires=libmagic/5.45
|
||||
RUN conan install -s build_type=${BUILD_TYPE} -b=* --requires=freetype/2.14.1
|
||||
RUN conan install -s build_type=${BUILD_TYPE} -b=missing --requires=gtest/1.14.0
|
||||
RUN conan install -s build_type=${BUILD_TYPE} -b=missing --requires=libjpeg/9f
|
||||
WORKDIR /workspaces
|
||||
RUN rm -rf tmp
|
||||
|
||||
RUN apk add --no-cache \
|
||||
clang \
|
||||
clang-extra-tools \
|
||||
gdb \
|
||||
git
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
FROM alpine:3.21.3 AS build
|
||||
|
||||
# Build type used by conan. "Debug" and "Release" are supported.
|
||||
ARG BUILD_TYPE=Release
|
||||
|
||||
RUN mkdir -p /root/.local/bin
|
||||
ENV PATH="${PATH}:/root/.local/bin"
|
||||
|
||||
# Install packages needed in both building and running.
|
||||
RUN apk update && \
|
||||
apk add --no-cache \
|
||||
libstdc++
|
||||
|
||||
# Install tools required for building.
|
||||
RUN apk add --no-cache \
|
||||
autoconf \
|
||||
bash \
|
||||
build-base \
|
||||
cmake \
|
||||
clang \
|
||||
clang-extra-tools \
|
||||
gdb \
|
||||
git \
|
||||
libtool \
|
||||
linux-headers \
|
||||
ninja \
|
||||
m4 \
|
||||
perl \
|
||||
python3 \
|
||||
pipx && \
|
||||
pipx ensurepath
|
||||
RUN pipx install conan && \
|
||||
conan profile detect
|
||||
|
||||
# Pre-build some conan packages we require and take a lot of time to build. This to
|
||||
# avoid expensive rebuilding everytime we need a fresh dev container..
|
||||
RUN mkdir -p /workspaces/tmp
|
||||
WORKDIR /workspaces/tmp
|
||||
RUN conan install -s build_type=${BUILD_TYPE} -b=* --requires=boost/1.89.0
|
||||
RUN conan install -s build_type=${BUILD_TYPE} -b=* --requires=libmagic/5.45
|
||||
RUN conan install -s build_type=${BUILD_TYPE} -b=* --requires=freetype/2.14.1
|
||||
RUN conan install -s build_type=${BUILD_TYPE} -b=missing --requires=gtest/1.14.0
|
||||
RUN conan install -s build_type=${BUILD_TYPE} -b=missing --requires=libjpeg/9f
|
||||
WORKDIR /workspaces
|
||||
RUN rm -rf tmp
|
||||
|
||||
# Remove the temporary workspace, build results for the conan packages remain cached.
|
||||
WORKDIR /workspaces
|
||||
RUN rm -rf tmp && \
|
||||
mkdir network-experiment && \
|
||||
mkdir build && \
|
||||
mkdir install
|
||||
|
||||
# Build and install
|
||||
WORKDIR /workspaces/network-experiment
|
||||
ADD . .
|
||||
RUN conan install /workspaces/network-experiment -of /workspaces/build -s build_type=${BUILD_TYPE}
|
||||
RUN cmake -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -S . -B /workspaces/build --install-prefix=/workspaces/install
|
||||
RUN cmake --build /workspaces/build --parallel
|
||||
RUN cmake --install /workspaces/build
|
||||
|
||||
|
||||
|
||||
FROM alpine:3.21.3
|
||||
|
||||
RUN apk update && \
|
||||
apk add --no-cache \
|
||||
libstdc++
|
||||
|
||||
RUN mkdir -p /workspaces/network-experiment/applications/http-mandelbrot/html
|
||||
|
||||
COPY --from=build /workspaces/install/bin/http-mandelbrot /bin
|
||||
COPY --from=build /workspaces/install/bin/magic.mgc /bin
|
||||
COPY --from=build /workspaces/network-experiment/applications/http-mandelbrot/html /workspaces/network-experiment/applications/http-mandelbrot/html
|
||||
|
||||
WORKDIR /bin
|
||||
ENTRYPOINT ["http-mandelbrot"]
|
||||
EXPOSE 9800/tcp
|
||||
|
||||
|
||||
Loading…
Reference in New Issue