xo-umbrella2/xo-websock/include/xo/websock/UrlRouter.hpp

148 lines
6 KiB
C++

/** @file UrlRouter.hpp
*
* @author Roland Conybeare, Sep 2026
**/
#pragma once
#include "DynamicEndpoint.hpp"
#include "EndpointKind.hpp"
#include <xo/webutil/HttpEndpointDescr.hpp>
#include <xo/webutil/StreamEndpointDescr.hpp>
#include <xo/refcnt/Refcounted.hpp>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
namespace xo {
namespace reflect { class TypeDescrTable; }
namespace web {
/** @brief server-wide: which endpoint serves a uri or stream name.
*
* Holds the webserver's registered endpoints, http and stream kept
* apart, and the matching that picks one for an incoming uri.
* Extracted from WebserverImpl; see .xo-backlog/xo-websock/issues/07.
*
* Both maps are keyed by stem -- the longest literal prefix of an
* endpoint's uri pattern, see EndpointUtil::stem(). E.g. endpoint
* with pattern
* /fixed/stem/${a}/more
* is stored under
* /fixed/stem/
* An http endpoint and a stream endpoint may share a stem.
*
* Registering a stem already present in the same map is an error;
* replacing an endpoint takes an explicit unregister first.
* "Same stem" is coarser than "same pattern": /fw/${a} and
* /fw/${a}/detail both have stem /fw/, so the second is rejected.
*
* Unregistering drops only the router's reference. Anyone else
* holding the endpoint -- e.g. a live subscription -- keeps it alive.
*
* THREADING: all methods may be called from any thread.
* Registration typically runs on the application's thread, lookup on
* the webserver's service thread.
**/
class UrlRouter {
public:
/** describe UrlRouter to xo-reflect, using @p table.
**/
static void reflect_self(reflect::TypeDescrTable * table);
/** register http endpoint described by @p descr.
* Throws std::runtime_error if an http endpoint with the same
* stem is already registered.
**/
void register_http(HttpEndpointDescr const & descr);
/** register stream endpoint described by @p descr.
* Throws std::runtime_error if a stream endpoint with the same
* stem is already registered.
**/
void register_stream(StreamEndpointDescr const & descr);
/** remove the http endpoint registered with exactly @p uri_pattern,
* and return it. Returns null, changing nothing, if there is
* none -- including when a DIFFERENT pattern with the same stem
* is registered.
**/
rp<DynamicEndpoint> unregister_http(std::string const & uri_pattern);
/** remove the stream endpoint registered with exactly
* @p uri_pattern, and return it -- the caller ends that
* endpoint's live subscriptions. Returns null, changing nothing,
* if there is none; see unregister_http().
**/
rp<DynamicEndpoint> unregister_stream(std::string const & uri_pattern);
/** call @p fn on every registered endpoint, http then stream,
* each by stem. For introspection.
*
* @p fn runs WITH the router's lock held, so it must not call
* back into this router (register, unregister, find). It sees
* the endpoint itself -- no rp<> copy is taken, so its
* reference count is as the rest of the program holds it.
**/
void visit_endpoints(EndpointVisitor const & fn) const;
/** http endpoint serving @p uri; null if none. See find_in(). **/
rp<DynamicEndpoint> find_http(std::string const & uri) const;
/** stream endpoint serving @p stream_name; null if none.
* See find_in().
**/
rp<DynamicEndpoint> find_stream(std::string const & stream_name) const;
private:
using EndpointMap = std::unordered_map<std::string,
rp<DynamicEndpoint>>;
/** find longest prefix of @p uri that is a stem in @p ep_map:
*
* 1. try the whole uri
* 2. try successively shorter prefixes of uri that end in '/'
* 3. try successively shorter prefixes of uri that do not end in '/'
*
* Result copied out under the lock: caller uses the endpoint with
* the lock released, since endpoint functions may re-enter the
* server.
**/
rp<DynamicEndpoint> find_in(std::string const & uri,
EndpointMap const & ep_map) const;
/** insert @p endpoint into @p *p_ep_map; @p kind ("http" or
* "stream") names the map in the duplicate error
**/
void insert_in(rp<DynamicEndpoint> endpoint,
char const * kind,
EndpointMap * p_ep_map);
/** remove the endpoint in @p *p_ep_map registered with exactly
* @p uri_pattern, and return it; null if none
**/
rp<DynamicEndpoint> erase_in(std::string const & uri_pattern,
EndpointMap * p_ep_map);
private:
/* reads private members, for "_members_" (UrlRouter.cpp) */
friend class JsonPrinter_UrlRouter;
private:
/* guards .http_map, .stream_map */
mutable std::mutex mutex_;
/* map :: stem -> http endpoint
* use .register_http() to insert
*/
EndpointMap http_map_;
/* map :: stem -> stream endpoint
* use .register_stream() to insert
*/
EndpointMap stream_map_;
}; /*UrlRouter*/
} /*namespace web*/
} /*namespace xo*/
/* end UrlRouter.hpp */