Motion Master 6.0.0-alpha.86
Next-generation motion control software
Loading...
Searching...
No Matches
mm::api Namespace Reference

HTTP-transport glue shared by the built-in server and route plug-in libs. More...

Classes

class  Request
 A request, snapshotted on the loop thread so a handler can outlive it. More...
 
struct  Response
 A complete response, produced off the loop and written by the framework. More...
 
struct  RouteContext
 The live collaborators a route plug-in needs, handed to it at registration time. More...
 
class  Router
 Registers routes whose handlers run off the event loop. More...
 

Typedefs

using Handler = std::function< Response(const Request &)>
 What a route does: a pure function from a snapshotted request to a response.
 
using RegisterRoutesFn = std::function< void(Router &router, const RouteContext &ctx)>
 A route plug-in: registers its routes on the HTTP app.
 

Functions

std::string percentDecode (std::string_view text)
 Percent-decodes a URL path component — %20 to a space, %2F to a slash.
 
Response json (const nlohmann::json &body)
 A 200 response carrying body as JSON.
 
Response bytes (std::string contentType, std::string body)
 A 200 response carrying body verbatim under contentType.
 
Response error (std::string status, std::string_view message)
 A status response carrying a {"error": message} body.
 
Response badRequest (std::string_view message)
 A 400 with message — the most common failure, so it gets a name.
 
Response notFound (std::string_view message)
 A 404 with message.
 
Response statusOnly (std::string status)
 A bare status response with no body — a 202 that means "under way", a 204 delete.
 
Response withWireTime (Response response, std::chrono::microseconds wireUs)
 Attaches the server-measured device time (X-Wire-Us) to response and returns it.
 
std::vector< std::string > parameterNames (std::string_view pattern)
 The :name tokens of a route pattern, in the order uWS will index them.
 
template<typename Op >
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.
 
template<typename Res >
Res * setCorsOrigin (Res *res, std::string_view corsOrigin)
 Writes the Access-Control-Allow-Origin header and returns res for chaining.
 
template<typename Res >
Res * setWireTime (Res *res, std::chrono::microseconds wireUs)
 Attaches the server-measured wire-time header (X-Wire-Us, microseconds) to a response.
 
template<typename Res >
void sendJson (Res *res, std::string_view corsOrigin, const nlohmann::json &body)
 Writes body as a 200 application/json response with the CORS header.
 
template<typename Res >
void sendBytes (Res *res, std::string_view corsOrigin, std::string_view contentType, std::string_view body)
 Writes body verbatim as a 200 response with the given contentType and the CORS header.
 
template<typename Res >
void sendError (Res *res, std::string_view status, std::string_view corsOrigin, std::string_view message, std::optional< std::chrono::microseconds > wireUs=std::nullopt)
 Writes a status response carrying a {"error": message} JSON body and the CORS header.
 
template<typename Res >
void sendStatus (Res *res, std::string_view status, std::string_view corsOrigin, std::optional< std::chrono::microseconds > wireUs=std::nullopt)
 Writes a bare status response (no body) with the CORS header.
 

Detailed Description

HTTP-transport glue shared by the built-in server and route plug-in libs.

The response helpers here write directly to a uWS::HttpResponse, which is only valid on the event-loop thread — so they are for the handful of framework-level responses that genuinely run there (the OPTIONS preflight, the HTML index, the catch-all 404). **Everything that serves the API goes through mm::api::Router instead**, whose handlers return a Response value and run off the loop. A timed variant of these once existed and was used by every device endpoint; it was removed with the last of them, because a shared header offering the blocking shape is how a fixed bug comes back.

This layer sits above mm::node (the transport-agnostic domain layer) and below the app: it is the one place that knows about uWebSockets. mm::node must never depend on it. It exists so a route plug-in lib (e.g. mm::example) can register endpoints with the exact same response shape (content type + CORS) as the built-in routes without depending on the app.

Typedef Documentation

◆ Handler

using mm::api::Handler = typedef std::function<Response(const Request&)>

What a route does: a pure function from a snapshotted request to a response.

◆ RegisterRoutesFn

using mm::api::RegisterRoutesFn = typedef std::function<void(Router& router, const RouteContext& ctx)>

A route plug-in: registers its routes on the HTTP app.

Called once, on the HTTP server's event-loop thread, after the built-in routes and before the CORS preflight, the catch-all 404, and listen(). A plug-in should register only its own specific paths (e.g. /api/example/...); the server owns the OPTIONS /api/* preflight and the /* fallthrough. Registration order does not affect matching — uWS routes by specificity — but a plug-in must not claim /api/* or /* wildcards.

Wire one up in the composition root with HttpServer::addRoutes (before start()). A plug-in is handed the Router rather than the raw app, so its handlers run off the event loop like every built-in route. That is the point of passing it: a plug-in that took the app could register a handler doing bus I/O on the loop thread and stall the whole API, which is the bug the Router exists to make unrepresentable — and a shared header offering the unsafe path is how it would come back.

Function Documentation

◆ badRequest()

Response mm::api::badRequest ( std::string_view  message)

A 400 with message — the most common failure, so it gets a name.

◆ bytes()

Response mm::api::bytes ( std::string  contentType,
std::string  body 
)

A 200 response carrying body verbatim under contentType.

◆ error()

Response mm::api::error ( std::string  status,
std::string_view  message 
)

A status response carrying a {"error": message} body.

◆ json()

Response mm::api::json ( const nlohmann::json &  body)

A 200 response carrying body as JSON.

Serialised with the replace error handler so a string value carrying non-UTF-8 bytes (a garbage VISIBLE_STRING from a misbehaving device) renders as U+FFFD rather than throwing — and an uncaught throw here would take the server down.

◆ notFound()

Response mm::api::notFound ( std::string_view  message)

A 404 with message.

◆ parameterNames()

std::vector< std::string > mm::api::parameterNames ( std::string_view  pattern)

The :name tokens of a route pattern, in the order uWS will index them.

uWS addresses path parameters positionally — req->getParameter(0); naming them is this layer's doing, so a handler asks for parameter("slavePosition") and stays correct when a route gains a segment ahead of it. A name runs to the next / or to the end of the pattern, so a trailing parameter needs no terminator, and a pattern with none (/api/user-cache/*) yields nothing.

Declared here rather than kept file-local so the mapping can be tested directly: it is the one piece of pattern parsing this layer does itself, and a route whose names come out shifted by one would still compile, still serve, and answer with another parameter's value.

◆ percentDecode()

std::string mm::api::percentDecode ( std::string_view  text)

Percent-decodes a URL path component — %20 to a space, %2F to a slash.

RFC 3986 §2.1 calls the mechanism percent-encoding and its unit a percent-encoded octet (a triplet: % and two hex digits); it names the inverse only as "decoding" those octets. The name here follows the WHATWG URL Standard, which calls the operation percent-decode. Not urlDecode, deliberately — that reads as though it folds + into a space, which is exactly what this must not do.

For path components, and deliberately not shared with query decoding: a query decoder also maps + to a space (application/x-www-form-urlencoded), which in a path is a literal +, so decoding a+b.zip that way would look up a b.zip. Request::query handles queries; this handles paths, which uWS hands over still encoded.

An invalid escape (%4Z, a trailing %, %4) is left verbatim rather than dropped, so a name containing one arrives intact at whatever resolves it and either names something real or fails cleanly — instead of silently becoming a different name.

The result may contain a NUL, from %00, and its length is authoritative: never treat it as a C string, or such a name truncates into a different one.

◆ sendBytes()

template<typename Res >
void mm::api::sendBytes ( Res *  res,
std::string_view  corsOrigin,
std::string_view  contentType,
std::string_view  body 
)

Writes body verbatim as a 200 response with the given contentType and the CORS header.

The raw-bytes analogue of sendJson: for a response whose body is already serialized (an octet-stream dump, a verbatim on-disk file, a text/yaml spec) rather than a nlohmann::json value — body is sent as-is. For anything richer — an extra header (e.g. Content-Disposition), a non-200 status, or backpressure-aware streaming via tryEnd — drop to setCorsOrigin + writeHeader directly instead.

◆ sendError()

template<typename Res >
void mm::api::sendError ( Res *  res,
std::string_view  status,
std::string_view  corsOrigin,
std::string_view  message,
std::optional< std::chrono::microseconds >  wireUs = std::nullopt 
)

Writes a status response carrying a {"error": message} JSON body and the CORS header.

Pass wireUs to attach the X-Wire-Us timing header to the failure the same way a success carries it — a failed device transaction still consumed wire time (an SDO read that waits out the mailbox timeout, a partial FoE transfer), and the client renders it identically. The header is written after writeStatus() so uWS keeps the non-200 status (headers-before-status would force a default 200).

◆ sendJson()

template<typename Res >
void mm::api::sendJson ( Res *  res,
std::string_view  corsOrigin,
const nlohmann::json &  body 
)

Writes body as a 200 application/json response with the CORS header.

Uses the replace error handler so a string-typed value carrying non-UTF-8 bytes (e.g. a garbage VISIBLE_STRING from a misbehaving device) is rendered with U+FFFD instead of throwing — an uncaught throw on the uWS loop terminates the whole server.

◆ sendStatus()

template<typename Res >
void mm::api::sendStatus ( Res *  res,
std::string_view  status,
std::string_view  corsOrigin,
std::optional< std::chrono::microseconds >  wireUs = std::nullopt 
)

Writes a bare status response (no body) with the CORS header.

wireUs optionally attaches the server-measured operation time, exactly as sendError does — an endpoint whose success case carries no body (a 204 No Content delete) still spent time doing the work, and the client shows it the same way as for a body-bearing response.

◆ setCorsOrigin()

template<typename Res >
Res * mm::api::setCorsOrigin ( Res *  res,
std::string_view  corsOrigin 
)

Writes the Access-Control-Allow-Origin header and returns res for chaining.

The single home for the origin-policy header. sendJson/sendError/sendStatus emit it for the common JSON paths; call this directly for a hand-rolled response that can't use them — a raw octet-stream body, a text/yaml dump, or a bespoke status line — so the header name and policy live in exactly one place. Order-independent among headers; call before end().

◆ setWireTime()

template<typename Res >
Res * mm::api::setWireTime ( Res *  res,
std::chrono::microseconds  wireUs 
)

Attaches the server-measured wire-time header (X-Wire-Us, microseconds) to a response.

wireUs is the server-measured time spent on the device-side operation itself — control-plane lock acquire plus the mailbox/ESC wire transaction(s) — not the end-to-end HTTP round-trip, which a cross-origin browser client observes as much larger (TLS + transport overhead). Reporting it lets the client attribute the device cost to the device and the remainder to the browser/transport. For a single-transaction endpoint (one SDO/FoE/register access) it is essentially the pure wire round-trip; for a multi-transaction one (object-dictionary enumeration, PDO-mapping read/write) it is the total across all of the operation's transactions. Because the value rides a header rather than the body, it is the one uniform timing channel that works for any response shape (JSON or raw octet-stream) without touching each endpoint's body schema. The header is CORS-exposed so the PWA can read it cross-origin. Emitted on both success and failure — a failed operation still consumed device time (e.g. an SDO read that waits out the mailbox timeout), and the client shows it the same way; failures route through sendError()'s wireUs parameter so the header lands after writeStatus(). Call before the body/end() (uWS requires all headers written before the body). Returns res for chaining, mirroring setCorsOrigin.

◆ statusOnly()

Response mm::api::statusOnly ( std::string  status)

A bare status response with no body — a 202 that means "under way", a 204 delete.

Named for what it produces rather than the plain status, which collides with the parameter name the neighbouring send* helpers already use for a status line.

◆ timed()

template<typename Op >
Response mm::api::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.

The shape of nearly every device endpoint, in one place: time the call, send the value as JSON with X-Wire-Us on success, or errorStatus with the same header on failure. Only op is timed, so body serialisation stays out of the figure.

◆ withWireTime()

Response mm::api::withWireTime ( Response  response,
std::chrono::microseconds  wireUs 
)

Attaches the server-measured device time (X-Wire-Us) to response and returns it.

The uniform timing channel: it rides a header rather than the body, so it works for any response shape, and it is emitted on failure as well as success because a failed device operation still consumed wire time. See the header's own documentation in web_api.h.