Motion Master 6.0.0-alpha.86
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 <functional>
10#include <nlohmann/json.hpp>
11#include <optional>
12#include <string>
13#include <string_view>
14#include <utility>
15#include <vector>
16
17namespace mm::api {
18
55
61class Request {
62 public:
63 Request(std::string url, std::vector<std::pair<std::string, std::string>> parameters,
64 std::string queryString, std::vector<std::pair<std::string, std::string>> headers,
65 std::string body)
66 : url_(std::move(url)),
67 parameters_(std::move(parameters)),
68 queryString_(std::move(queryString)),
69 headers_(std::move(headers)),
70 body_(std::move(body)) {}
71
73 std::string_view url() const { return url_; }
74
77 const std::string& body() const { return body_; }
78
83 std::string_view header(std::string_view name) const {
84 for (const auto& [key, value] : headers_) {
85 if (key == name) {
86 return value;
87 }
88 }
89 return {};
90 }
91
94 bool accepts(std::string_view contentType) const {
95 return header("accept").find(contentType) != std::string_view::npos;
96 }
97
105 std::string_view parameter(std::string_view name) const {
106 for (const auto& [key, value] : parameters_) {
107 if (key == name) {
108 return value;
109 }
110 }
111 return {};
112 }
113
129 std::optional<std::string> query(std::string_view key) const;
130
136 template <typename T>
137 std::optional<T> parameterAs(std::string_view name) const {
138 return parseNumber<T>(parameter(name));
139 }
140
142 template <typename T>
143 std::optional<T> queryAs(std::string_view key) const {
144 auto value = query(key);
145 return value ? parseNumber<T>(*value) : std::nullopt;
146 }
147
148 private:
149 template <typename T>
150 static std::optional<T> parseNumber(std::string_view text) {
151 if (text.empty()) {
152 return std::nullopt;
153 }
154 const bool hex = text.size() > 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X');
155 const std::string_view digits = hex ? text.substr(2) : text;
156 T value{};
157 const auto [ptr, ec] =
158 std::from_chars(digits.data(), digits.data() + digits.size(), value, hex ? 16 : 10);
159 if (ec != std::errc{} || ptr != digits.data() + digits.size()) { // NOLINT(whitespace/braces)
160 return std::nullopt;
161 }
162 return value;
163 }
164
165 std::string url_;
166 std::vector<std::pair<std::string, std::string>> parameters_;
167 std::string queryString_;
168 std::vector<std::pair<std::string, std::string>> headers_;
169 std::string body_;
170};
171
176struct Response {
177 std::string status = "200 OK";
178 std::string contentType = "application/json";
179 std::string body;
182 std::vector<std::pair<std::string, std::string>> headers;
183};
184
204std::string percentDecode(std::string_view text);
205
216std::vector<std::string> parameterNames(std::string_view pattern);
217
223Response json(const nlohmann::json& body);
224
226Response bytes(std::string contentType, std::string body);
227
229Response error(std::string status, std::string_view message);
230
232Response badRequest(std::string_view message);
233
235Response notFound(std::string_view message);
236
241Response statusOnly(std::string status);
242
248Response withWireTime(Response response, std::chrono::microseconds wireUs);
249
255template <typename Op>
256Response timed(Op&& op, std::string errorStatus = "500 Internal Server Error") {
257 const auto t0 = std::chrono::steady_clock::now();
258 auto result = std::forward<Op>(op)();
259 const auto wireUs =
260 std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - t0);
261 if (!result) {
262 return withWireTime(error(std::move(errorStatus), result.error()), wireUs);
263 }
264 return withWireTime(json(nlohmann::json(*result)), wireUs);
265}
266
268using Handler = std::function<Response(const Request&)>;
269
274class Router {
275 public:
282 Router(uWS::SSLApp& app, uWS::Loop* loop, BS::light_thread_pool& pool,
283 const std::atomic<bool>& stopping, std::string_view corsOrigin)
284 : app_(app), loop_(loop), pool_(pool), stopping_(stopping), corsOrigin_(corsOrigin) {}
285
287 void get(const std::string& pattern, Handler handler) {
288 add("GET", pattern, std::move(handler), /*hasBody=*/false);
289 }
291 void post(const std::string& pattern, Handler handler) {
292 add("POST", pattern, std::move(handler), /*hasBody=*/true);
293 }
295 void put(const std::string& pattern, Handler handler) {
296 add("PUT", pattern, std::move(handler), /*hasBody=*/true);
297 }
299 void del(const std::string& pattern, Handler handler) {
300 add("DELETE", pattern, std::move(handler), /*hasBody=*/false);
301 }
302
303 private:
304 void add(std::string_view method, const std::string& pattern, Handler handler, bool hasBody);
305
306 uWS::SSLApp& app_;
307 uWS::Loop* loop_;
308 BS::light_thread_pool& pool_;
320 const std::atomic<bool>& stopping_;
321 std::string_view corsOrigin_;
322};
323
324} // namespace mm::api
A request, snapshotted on the loop thread so a handler can outlive it.
Definition router.h:61
std::string_view url() const
The full request path.
Definition router.h:73
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:77
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:143
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:137
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:105
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:63
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:83
bool accepts(std::string_view contentType) const
Whether Accept asks for contentType. Substring, matching how these endpoints have always negotiated: ...
Definition router.h:94
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:274
void put(const std::string &pattern, Handler handler)
Registers handler for PUT pattern; the body is accumulated before dispatch.
Definition router.h:295
void post(const std::string &pattern, Handler handler)
Registers handler for POST pattern; the body is accumulated before dispatch.
Definition router.h:291
void del(const std::string &pattern, Handler handler)
Registers handler for DELETE pattern.
Definition router.h:299
void get(const std::string &pattern, Handler handler)
Registers handler for GET pattern. Path parameters are :name as in uWS.
Definition router.h:287
Router(uWS::SSLApp &app, uWS::Loop *loop, BS::light_thread_pool &pool, const std::atomic< bool > &stopping, std::string_view corsOrigin)
Definition router.h:282
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 bytes(std::string contentType, std::string body)
A 200 response carrying body verbatim under contentType.
Definition router.cc:147
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:256
Response error(std::string status, std::string_view message)
A status response carrying a {"error": message} body.
Definition router.cc:154
std::function< Response(const Request &)> Handler
What a route does: a pure function from a snapshotted request to a response.
Definition router.h:268
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
A complete response, produced off the loop and written by the framework.
Definition router.h:176
std::vector< std::pair< std::string, std::string > > headers
Definition router.h:182
std::string body
Definition router.h:179
std::string contentType
Definition router.h:178
std::string status
Definition router.h:177