From 999d77a9bcfa90f002b0107ea5fc7e6abc51dd9c Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Sat, 29 Aug 2026 14:36:36 +0200 Subject: Deployment preparations --- flake.nix | 24 +++++-- server/CMakeLists.txt | 2 +- server/src/config.cpp | 3 +- server/src/config.cppm | 2 +- server/src/http_server.cppm | 43 ++++++++----- server/src/main.cpp | 5 +- server/src/srv.cpp | 10 +-- server/src/srv.cppm | 6 +- todo.org | 1 + web/index.html | 2 +- web/index.js | 151 ++++++++++++++++++++++++++++++++++++++++++++ web/script.js | 149 ------------------------------------------- 12 files changed, 217 insertions(+), 181 deletions(-) create mode 100644 web/index.js delete mode 100644 web/script.js diff --git a/flake.nix b/flake.nix index 2320882..7629d56 100644 --- a/flake.nix +++ b/flake.nix @@ -21,8 +21,8 @@ }); boost = pkgs."boost${toString boostVersion}"; - routemon = stdenv.mkDerivation { - name = "routemon"; + routemon-server = stdenv.mkDerivation { + name = "routemon-server"; src = ./server; buildInputs = with pkgs; [ boost pugixml expat icu openssl sqlite ]; @@ -48,13 +48,29 @@ "-DCMAKE_CXX_STDLIB_MODULES_JSON=${libstdcxxGcc}/lib/libstdc++.modules.json" ]; }; + + routemon-web = pkgs.stdenvNoCC.mkDerivation { + name = "routemon-web"; + + src = ./web; + + installPhase = '' + runHook preInstall + + mkdir -p $out/usr/lib/routemon-web/htdocs + cp -rv index.html index.js style.css $out/usr/lib/routemon-web/htdocs + + runHook postInstall + ''; + }; in { - packages.routemon = routemon; + packages.routemon-server = routemon-server; + packages.routemon-web = routemon-web; devShells.default = pkgs.mkShell.override { inherit stdenv; } { buildInputs = [ pkgs.nix-index pkgs.nix-tree ]; - inputsFrom = [ routemon ]; + inputsFrom = [ routemon-server ]; }; formatter = pkgs.nixpkgs-fmt; diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 15ebfae..014ec7c 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -9,7 +9,7 @@ add_compile_options( -fno-omit-frame-pointer -Wall -Wextra -Wformat -Wformat=2 -Wconversion -Wimplicit-fallthrough -Werror=format-security - -U_FORTIFY_SOURCE # -D_FORTIFY_SOURCE=3 (unfortunately breaks the std module) + -U_FORTIFY_SOURCE # -D_FORTIFY_SOURCE=3 (unfortunately breaks the std module, see https://github.com/llvm/llvm-project/issues/121709) -D_GLIBCXX_ASSERTIONS -fstrict-flex-arrays=3 -fstack-clash-protection -fstack-protector-strong diff --git a/server/src/config.cpp b/server/src/config.cpp index 5c1a161..200b873 100644 --- a/server/src/config.cpp +++ b/server/src/config.cpp @@ -182,7 +182,8 @@ auto tag_invoke( [](object_reader& r) -> http_server { return { - .lax_cors = r.expect_at("lax_cors"), + .allow_origins = + r.expect_at>("allow_origins"), }; }); } diff --git a/server/src/config.cppm b/server/src/config.cppm index 358a961..ab9b137 100644 --- a/server/src/config.cppm +++ b/server/src/config.cppm @@ -23,7 +23,7 @@ export struct database export struct http_server { - bool lax_cors; + std::vector allow_origins; }; export struct logger diff --git a/server/src/http_server.cppm b/server/src/http_server.cppm index dc8c183..b16f3a8 100644 --- a/server/src/http_server.cppm +++ b/server/src/http_server.cppm @@ -15,7 +15,7 @@ export module routemon:http.server; import std; import :config; import :trace; -export import :http.common; +import :http.common; import :problem; namespace net = boost::asio; @@ -157,14 +157,33 @@ using middleware_t = ->net::awaitable>; template -auto lax_cors_middleware( - Ctx ctx, bhttp::request_header& req_hdr, - next_handler_t next) -> net::awaitable +auto id_middleware( + Ctx ctx, bhttp::request_header&, next_handler_t next) + -> net::awaitable { - std::ignore = req_hdr; - auto prersp = co_await next(ctx); - prersp.header().set(bhttp::field::access_control_allow_origin, "*"); - co_return std::move(prersp); + co_return co_await next(std::move(ctx)); +} + +template +auto cors_middleware(std::vector const& allow_origins) + -> middleware_t +{ + if (allow_origins.empty()) + return id_middleware; + + auto header_str = allow_origins + | std::views::join_with(std::string_view{", "}) + | std::ranges::to(); + + return [header_str = std::move(header_str)]( + Ctx ctx, bhttp::request_header& req_hdr, + next_handler_t next) -> net::awaitable + { + std::ignore = req_hdr; + auto prersp = co_await next(ctx); + prersp.header().set(bhttp::field::access_control_allow_origin, header_str); + co_return std::move(prersp); + }; } template @@ -310,14 +329,6 @@ auto default_options_handler( auto global_options_handler(base_ctx const& ctx, readable_request r) -> net::awaitable; -template -auto id_middleware( - Ctx ctx, bhttp::request_header&, next_handler_t next) - -> net::awaitable -{ - co_return co_await next(std::move(ctx)); -} - template auto middleware_compose(middleware_t ab, middleware_t bc) -> middleware_t diff --git a/server/src/main.cpp b/server/src/main.cpp index d5fcd42..b0a9938 100644 --- a/server/src/main.cpp +++ b/server/src/main.cpp @@ -105,8 +105,9 @@ auto real_main(std::span args) -> exit_status l.info("Loading situations finished in {}", dur_load); auto handler = routemon::api::handler{l, std::move(pub)}; - auto http_server = - routemon::srv::server{l, std::move(lsel), std::move(handler)}; + auto http_server = routemon::srv::server{ + l, config.http_server, std::move(lsel), std::move(handler) + }; http_server.spawn(ioc); ioc.run(); diff --git a/server/src/srv.cpp b/server/src/srv.cpp index 00a2bb8..b70f05c 100644 --- a/server/src/srv.cpp +++ b/server/src/srv.cpp @@ -232,22 +232,24 @@ auto handler::make_routes() -> http::route_tree> }); } -auto server::make_global_middleware() +auto server::make_global_middleware(config::http_server const& cfg) -> http::middleware_t { return http::middleware_compose< http::base_ctx, http::trace_id_ctx, http::trace_id_ctx >(http::trace_id_middleware, - http::lax_cors_middleware>); + http::cors_middleware>( + cfg.allow_origins)); } server::server( - log::logger const& l, locale::selector&& lsel, api::handler&& inner) + log::logger const& l, config::http_server const& cfg, + locale::selector&& lsel, api::handler&& inner) : handler_{std::move(inner)}, srv_{ l, http::router{ - std::move(lsel), make_global_middleware(), handler_.make_routes() + std::move(lsel), make_global_middleware(cfg), handler_.make_routes() } } { diff --git a/server/src/srv.cppm b/server/src/srv.cppm index 1d082fb..063617e 100644 --- a/server/src/srv.cppm +++ b/server/src/srv.cppm @@ -35,11 +35,13 @@ export class server handler handler_; http::server srv_; - static auto make_global_middleware() + static auto make_global_middleware(config::http_server const& cfg) -> http::middleware_t; public: - server(log::logger const& l, locale::selector&& lsel, api::handler&& inner); + server( + log::logger const& l, config::http_server const& cfg, + locale::selector&& lsel, api::handler&& inner); auto spawn(net::io_context& ioc) -> void; }; diff --git a/todo.org b/todo.org index ebddd19..91c02e3 100644 --- a/todo.org +++ b/todo.org @@ -1 +1,2 @@ - [ ] Accept-Language negotation does not work as expected +- [ ] Expect: 100-continue support in HTTP server? diff --git a/web/index.html b/web/index.html index f927d39..0fa0cea 100644 --- a/web/index.html +++ b/web/index.html @@ -47,6 +47,6 @@ - + diff --git a/web/index.js b/web/index.js new file mode 100644 index 0000000..316e73f --- /dev/null +++ b/web/index.js @@ -0,0 +1,151 @@ +const apiBaseUrl = "https://api.routemon.fautchen.eu"; + +const dialog = document.querySelector("dialog"); +const closeButton = document.querySelector("dialog button"); +closeButton.addEventListener("click", () => { + dialog.close(); +}); + +let map = L.map("map"); + +L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { + attribution: '© OpenStreetMap contributors', +}).addTo(map); + +let layerGroup = L.layerGroup().addTo(map); + +document.forms["gpx-upload-form"].addEventListener("submit", (event) => { + event.preventDefault(); + const gpxFileInput = document.getElementById("gpx-file-input"); + if (gpxFileInput.files.length !== 1) { + alert("Voor de upload moet er exact één GPX-bestand zijn geselecteerd"); + return; + } + load(gpxFileInput.files[0]); +}); + +function el(name, attrs, ...children) { + const node = document.createElement(name); + for (const [k, v] of Object.entries(attrs)) { + node.setAttribute(k, v); + } + node.replaceChildren(...children); + return node; +} + +function txt(s) { + return document.createTextNode(s); +} + +function tbl(rowcols) { + return el("table", {}, + el("tbody", {}, + ...rowcols.map((cols) => + el("tr", {}, + ...cols.map((col) => el("td", {}, col)), + )))); +} + +async function showProblem(rsp) { + const problem = await rsp.json(); + + document.getElementById("dialog-title").innerText = problem.title; + if (problem.detail) + document.getElementById("dialog-message").innerText = problem.detail; + else + document.getElementById("dialog-message").innerText = ""; + dialog.showModal(); + + let rows = [ + [txt("HTTP-statuscode"), + el("a", { "href": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/" + rsp.status }, + txt(rsp.status + " (" + rsp.statusText + ")"))], + [txt("Probleemtype"), + el("code", {}, txt(problem.type))], + [txt("Trace-ID"), + el("code", {}, txt(rsp.headers.get("X-Routemon-Trace-Id")))], + ]; + if (problem.instance) { + rows.push([txt("Instantie"), + el("code", {}, txt(problem.instance))]); + } + document.getElementById("dialog-more-info").replaceChildren(tbl(rows)); + + return; +} + +async function getSysinfo() { + const rsp = await fetch(apiBaseUrl + "/sysinfo", { + method: "GET", + headers: { + "Accept-Language": "nl-NL", + }, + }); + if (!rsp.ok) { + await showProblem(rsp); + return; + } + + const res = await rsp.json(); + document.getElementById("situation-publication-of").innerText = new Date(res.using_publication_of).toLocaleString(); +} + +async function load(gpxFile) { + const rsp = await fetch(apiBaseUrl + "/gpx", { + method: "POST", + headers: { + "Accept-Language": "nl-NL", + }, + body: gpxFile, + }); + if (!rsp.ok) { + await showProblem(rsp); + return; + } + + const res = await rsp.json(); + layerGroup.clearLayers(); + + let bounds = null; + for (const track of res.tracks) { + for (const segment of track.segments) { + const polyline = L.polyline(segment.points, { color: "blue" }).addTo(layerGroup); + if (bounds === null) { + bounds = polyline.getBounds(); + } else { + bounds.extend(polyline.getBounds()); + } + } + } + if (bounds !== null) { + map.fitBounds(bounds); + } + + res.relevant_situations.forEach((sit) => { + let firstComment = sit.comments[0] || ""; + const lfIndex = firstComment.indexOf("\n"); + if (lfIndex !== -1) { + firstComment = firstComment.substring(0, lfIndex); + } + const commentEl = + el("div", {}, + el("code", {}, txt(sit.id)), + txt(": " + firstComment)); + + const markerLayer = L.marker(sit.location); + markerLayer.addTo(layerGroup).bindPopup(commentEl); + + sit.relevant_road_closures.forEach((rc) => { + rc.relevant_lss.forEach((ls) => { + const lineLayer = L.polyline(ls, { + color: "purple", + dashArray: "5, 10", + dashOffset: "0", + }); + lineLayer.addTo(layerGroup); + }); + }); + }); +} + +getSysinfo(); diff --git a/web/script.js b/web/script.js deleted file mode 100644 index b3179b3..0000000 --- a/web/script.js +++ /dev/null @@ -1,149 +0,0 @@ -const dialog = document.querySelector("dialog"); -const closeButton = document.querySelector("dialog button"); -closeButton.addEventListener("click", () => { - dialog.close(); -}); - -let map = L.map("map"); - -L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { - attribution: '© OpenStreetMap contributors', -}).addTo(map); - -let layerGroup = L.layerGroup().addTo(map); - -document.forms["gpx-upload-form"].addEventListener("submit", (event) => { - event.preventDefault(); - const gpxFileInput = document.getElementById("gpx-file-input"); - if (gpxFileInput.files.length !== 1) { - alert("Voor de upload moet er exact één GPX-bestand zijn geselecteerd"); - return; - } - load(gpxFileInput.files[0]); -}); - -function el(name, attrs, ...children) { - const node = document.createElement(name); - for (const [k, v] of Object.entries(attrs)) { - node.setAttribute(k, v); - } - node.replaceChildren(...children); - return node; -} - -function txt(s) { - return document.createTextNode(s); -} - -function tbl(rowcols) { - return el("table", {}, - el("tbody", {}, - ...rowcols.map((cols) => - el("tr", {}, - ...cols.map((col) => el("td", {}, col)), - )))); -} - -async function showProblem(rsp) { - const problem = await rsp.json(); - - document.getElementById("dialog-title").innerText = problem.title; - if (problem.detail) - document.getElementById("dialog-message").innerText = problem.detail; - else - document.getElementById("dialog-message").innerText = ""; - dialog.showModal(); - - let rows = [ - [txt("HTTP-statuscode"), - el("a", { "href": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/" + rsp.status }, - txt(rsp.status + " (" + rsp.statusText + ")"))], - [txt("Probleemtype"), - el("code", {}, txt(problem.type))], - [txt("Trace-ID"), - el("code", {}, txt(rsp.headers.get("X-Routemon-Trace-Id")))], - ]; - if (problem.instance) { - rows.push([txt("Instantie"), - el("code", {}, txt(problem.instance))]); - } - document.getElementById("dialog-more-info").replaceChildren(tbl(rows)); - - return; -} - -async function getSysinfo() { - const rsp = await fetch("http://localhost:8284/sysinfo", { - method: "GET", - headers: { - "Accept-Language": "nl-NL", - }, - }); - if (!rsp.ok) { - await showProblem(rsp); - return; - } - - const res = await rsp.json(); - document.getElementById("situation-publication-of").innerText = new Date(res.using_publication_of).toLocaleString(); -} - -async function load(gpxFile) { - const rsp = await fetch("http://localhost:8284/gpx", { - method: "POST", - headers: { - "Accept-Language": "nl-NL", - }, - body: gpxFile, - }); - if (!rsp.ok) { - await showProblem(rsp); - return; - } - - const res = await rsp.json(); - layerGroup.clearLayers(); - - let bounds = null; - for (const track of res.tracks) { - for (const segment of track.segments) { - const polyline = L.polyline(segment.points, { color: "blue" }).addTo(layerGroup); - if (bounds === null) { - bounds = polyline.getBounds(); - } else { - bounds.extend(polyline.getBounds()); - } - } - } - if (bounds !== null) { - map.fitBounds(bounds); - } - - res.relevant_situations.forEach((sit) => { - let firstComment = sit.comments[0] || ""; - const lfIndex = firstComment.indexOf("\n"); - if (lfIndex !== -1) { - firstComment = firstComment.substring(0, lfIndex); - } - const commentEl = - el("div", {}, - el("code", {}, txt(sit.id)), - txt(": " + firstComment)); - - const markerLayer = L.marker(sit.location); - markerLayer.addTo(layerGroup).bindPopup(commentEl); - - sit.relevant_road_closures.forEach((rc) => { - rc.relevant_lss.forEach((ls) => { - const lineLayer = L.polyline(ls, { - color: "purple", - dashArray: "5, 10", - dashOffset: "0", - }); - lineLayer.addTo(layerGroup); - }); - }); - }); -} - -getSysinfo(); -- cgit v1.3