Motion Master 6.0.0-alpha.111
Next-generation motion control software
Loading...
Searching...
No Matches
router.h
Go to the documentation of this file.
1#pragma once
2
3#include <uwebsockets/App.h>
4
5#include <BS_thread_pool.hpp>
6#include <atomic>
7#include <charconv>
8#include <chrono>
9#include <concepts>
10#include <functional>
11#include <nlohmann/json.hpp>
12#include <optional>
13#include <string>
14#include <string_view>
15#include <type_traits>
16#include <utility>
17#include <vector>
18
19namespace mm::api {
20
57
63class Request {
64 public:
65 Request(std::string url, std::vector<std::pair<std::string, std::string>> parameters,
66 std::string queryString, std::vector<std::pair<std::string, std::string>> headers,
67 std::string body)
68 : url_(std::move(url)),
69 parameters_(std::move(parameters)),
70 queryString_(std::move(queryString)),
71 headers_(std::move(headers)),
72 body_(std::move(body)) {}
73
75 std::string_view url() const { return url_; }
76
79 const std::string& body() const { return body_; }
80
85 std::string_view header(std::string_view name) const {
86 for (const auto& [key, value] : headers_) {
87 if (key == name) {
88 return value;
89 }
90 }
91 return {};
92 }
93
96 bool accepts(std::string_view contentType) const {
97 return header("accept").find(contentType) != std::string_view::npos;
98 }
99
107 std::string_view parameter(std::string_view name) const {
108 for (const auto& [key, value] : parameters_) {
109 if (key == name) {
110 return value;
111 }
112 }
113 return {};
114 }
115
131 std::optional<std::string> query(std::string_view key) const;
132
138 template <typename T>
139 std::optional<T> parameterAs(std::string_view name) const {
140 return parseNumber<T>(parameter(name));
141 }
142
144 template <typename T>
145 std::optional<T> queryAs(std::string_view key) const {
146 auto value = query(key);
147 return value ? parseNumber<T>(*value) : std::nullopt;
148 }
149
150 private:
151 template <typename T>
152 static std::optional<T> parseNumber(std::string_view text) {
153 if (text.empty()) {
154 return std::nullopt;
155 }
156 const bool hex = text.size() > 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X');
157 const std::string_view digits = hex ? text.substr(2) : text;
158 T value{};
159 const auto [ptr, ec] =
160 std::from_chars(digits.data(), digits.data() + digits.size(), value, hex ? 16 : 10);
161 if (ec != std::errc{} || ptr != digits.data() + digits.size()) { // NOLINT(whitespace/braces)
162 return std::nullopt;
163 }
164 return value;
165 }
166
167 std::string url_;
168 std::vector<std::pair<std::string, std::string>> parameters_;
169 std::string queryString_;
170 std::vector<std::pair<std::string, std::string>> headers_;
171 std::string body_;
172};
173
178struct Response {
179 std::string status = "200 OK";
180 std::string contentType = "application/json";
181 std::string body;
184 std::vector<std::pair<std::string, std::string>> headers;
185};
186
206std::string percentDecode(std::string_view text);
207
218std::vector<std::string> parameterNames(std::string_view pattern);
219
225Response json(const nlohmann::json& body);
226
228Response bytes(std::string contentType, std::string body);
229
231Response error(std::string status, std::string_view message);
232
234Response badRequest(std::string_view message);
235
237Response notFound(std::string_view message);
238
243Response statusOnly(std::string status);
244
250Response withWireTime(Response response, std::chrono::microseconds wireUs);
251
257template <typename Op>
258Response timed(Op&& op, std::string errorStatus = "500 Internal Server Error") {
259 const auto t0 = std::chrono::steady_clock::now();
260 auto result = std::forward<Op>(op)();
261 const auto wireUs =
262 std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - t0);
263 if (!result) {
264 return withWireTime(error(std::move(errorStatus), result.error()), wireUs);
265 }
266 return withWireTime(json(nlohmann::json(*result)), wireUs);
267}
268
274template <typename Op, typename StatusOf>
275 requires std::invocable<StatusOf&, const typename std::invoke_result_t<Op&>::error_type&>
276Response timed(Op&& op, StatusOf&& statusOf) {
277 const auto t0 = std::chrono::steady_clock::now();
278 auto result = std::forward<Op>(op)();
279 const auto wireUs =
280 std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - t0);
281 if (!result) {
282 return withWireTime(error(std::string(statusOf(result.error())), result.error().message),
283 wireUs);
284 }
285 return withWireTime(json(nlohmann::json(*result)), wireUs);
286}
287
289using Handler = std::function<Response(const Request&)>;
290
295class Router {
296 public:
303 Router(uWS::SSLApp& app, uWS::Loop* loop, BS::light_thread_pool& pool,
304 const std::atomic<bool>& stopping, std::string_view corsOrigin)
305 : app_(app), loop_(loop), pool_(pool), stopping_(stopping), corsOrigin_(corsOrigin) {}
306
308 void get(const std::string& pattern, Handler handler) {
309 add("GET", pattern, std::move(handler), /*hasBody=*/false);
310 }
312 void post(const std::string& pattern, Handler handler) {
313 add("POST", pattern, std::move(handler), /*hasBody=*/true);
314 }
316 void put(const std::string& pattern, Handler handler) {
317 add("PUT", pattern, std::move(handler), /*hasBody=*/true);
318 }
320 void del(const std::string& pattern, Handler handler) {
321 add("DELETE", pattern, std::move(handler), /*hasBody=*/false);
322 }
323
324 private:
325 void add(std::string_view method, const std::string& pattern, Handler handler, bool hasBody);
326
327 uWS::SSLApp& app_;
328 uWS::Loop* loop_;
329 BS::light_thread_pool& pool_;
341 const std::atomic<bool>& stopping_;
342 std::string_view corsOrigin_;
343};
344
345} // namespace mm::api
A request, snapshotted on the loop thread so a handler can outlive it.
Definition router.h:63
std::string_view url() const
The full request path.
Definition router.h:75
const std::string & body() const
The request body, empty for methods that carry none. Complete — a handler never sees a partial body,...
Definition router.h:79
std::optional< T > queryAs(std::string_view key) const
A query value parsed as an integer, or std::nullopt if absent or not one.
Definition router.h:145
std::optional< T > parameterAs(std::string_view name) const
A path parameter parsed as an integer, or std::nullopt if it is absent or not one.
Definition router.h:139
std::string_view parameter(std::string_view name) const
A path parameter by the name the route pattern declared (:slavePosition), or empty.
Definition router.h:107
Request(std::string url, std::vector< std::pair< std::string, std::string > > parameters, std::string queryString, std::vector< std::pair< std::string, std::string > > headers, std::string body)
Definition router.h:65
std::string_view header(std::string_view name) const
A request header by name, lower-cased as uWS delivers it, or empty when absent.
Definition router.h:85
bool accepts(std::string_view contentType) const
Whether Accept asks for contentType. Substring, matching how these endpoints have always negotiated: ...
Definition router.h:96
std::optional< std::string > query(std::string_view key) const
A query-string value by key, percent-decoded, or std::nullopt when it has no value.
Definition router.cc:102
Registers routes whose handlers run off the event loop.
Definition router.h:295
void put(const std::string &pattern, Handler handler)
Registers handler for PUT pattern; the body is accumulated before dispatch.
Definition router.h:316
void post(const std::string &pattern, Handler handler)
Registers handler for POST pattern; the body is accumulated before dispatch.
Definition router.h:312
void del(const std::string &pattern, Handler handler)
Registers handler for DELETE pattern.
Definition router.h:320
void get(const std::string &pattern, Handler handler)
Registers handler for GET pattern. Path parameters are :name as in uWS.
Definition router.h:308
Router(uWS::SSLApp &app, uWS::Loop *loop, BS::light_thread_pool &pool, const std::atomic< bool > &stopping, std::string_view corsOrigin)
Definition router.h:303
mailboxStatusFmmu useCompleteAccess parameters
Definition config.h:245
std::string name
Catalogue name, e.g. "sm2".
Definition eni_request.cc:37
HTTP-transport glue shared by the built-in server and route plug-in libs.
Definition router.cc:14
Response badRequest(std::string_view message)
A 400 with message — the most common failure, so it gets a name.
Definition router.cc:161
std::vector< std::string > parameterNames(std::string_view pattern)
The :name tokens of a route pattern, in the order uWS will index them.
Definition router.cc:176
std::string percentDecode(std::string_view text)
Percent-decodes a URL path component — %20 to a space, %2F to a slash.
Definition router.cc:119
Response timed(Op &&op, std::string errorStatus="500 Internal Server Error")
Runs a device operation, times it, and turns its std::expected into a timed response.
Definition router.h:258
std::function< Response(const Request &)> Handler
What a route does: a pure function from a snapshotted request to a response.
Definition router.h:289
Response statusOnly(std::string status)
A bare status response with no body — a 202 that means "under way", a 204 delete.
Definition router.cc:165
Response json(const nlohmann::json &body)
A 200 response carrying body as JSON.
Definition router.cc:140
Response withWireTime(Response response, std::chrono::microseconds wireUs)
Attaches the server-measured device time (X-Wire-Us) to response and returns it.
Definition router.cc:170
Response notFound(std::string_view message)
A 404 with message.
Definition router.cc:163
std::string text
Definition soem_fieldbus_driver.cc:1254
FoeError error
Definition soem_fieldbus_driver.cc:1307
std::size_t bytes
Definition spoe_connection.cc:24
A complete response, produced off the loop and written by the framework.
Definition router.h:178
std::vector< std::pair< std::string, std::string > > headers
Definition router.h:184
std::string body
Definition router.h:181
std::string contentType
Definition router.h:180
std::string status
Definition router.h:179