Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Router.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Framework_WebServer_Router_h_
5#define _Stroika_Framework_WebServer_Router_h_ 1
6
7#include "Stroika/Frameworks/StroikaPreComp.h"
8
11#include "Stroika/Foundation/Containers/Sequence.h"
12
13#include "Stroika/Frameworks/WebServer/CORS.h"
14#include "Stroika/Frameworks/WebServer/Interceptor.h"
15#include "Stroika/Frameworks/WebServer/Request.h"
16#include "Stroika/Frameworks/WebServer/RequestHandler.h"
17
18/*
19 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
20 *
21 * \note Inspired by, but fairly different from
22 * @see http://guides.rubyonrails.org/routing.html
23 *
24 * Design Choice Notes:
25 *
26 * o RequestHandler::CanHandleRequest() -> bool method, or association list of PATTERN->RequestHandler?
27 *
28 * We COULD either have a special RequestHandler maintaining a list of owned RequestHandler, with KEYs
29 * of patterns like with Django:
30 *
31 * extra_patterns = patterns('',
32 * url(r'^reports/(?P<id>\d+)/$', 'credit.views.report', name='credit-reports'),
33 * url(r'^charge/$', 'credit.views.charge', name='credit-charge'),
34 * )
35 *
36 * However, in terms of performance and flexibility its not clear the regular expression matching would be any more efficient or easier to code than
37 * just having a C++ method on RequestHandler().
38 */
39
41
42 using namespace Stroika::Foundation;
43
48
49 class Router;
50
51 /**
52 *
53 * &&& HEAD and OPTIONS can have routes to be overridden. But by default they are handled automatically by the router.
54 *
55 * @todo - we probably want to add ability to generically parse out arguments from url, and include them to handler (as rails does - handy for ID in REST)
56 *
57 * @todo need more generic matching to fit in (maybe optional matcher that takes URL?, or even full Request).
58 *
59 * NOTE - may verb match and path match each OPTIONALS in class and have maybe a LIST of THINGS we know how to match.
60 * VERB
61 * RELPATH
62 * HTTP HEADER (like SOAPACTION)
63 * RequestObject????
64 * then we use "Route" to make generic the mapping of a request to a Handler.
65 *
66 * @todo NEED to support NESTED Routes (or aggregated).
67 * Key is need stuff like 'default error handling' - and just to somehow inherit/copy that.
68 * (reconsider - maybe easy now that I have 'handled' flag)...
69 *
70 * \note \em Thread-Safety <a href="Thread-Safety.md#C++-Standard-Thread-Safety-For-Envelope-Letter-Internally-Synchronized">C++-Standard-Thread-Safety-For-Envelope-Letter-Internally-Synchronized</a>
71 * But note that Matches() is a const method, so it can safely be called from any number of threads
72 * simultaneously.
73 *
74 * \pre it is expected aggregated handlers provided MUST be <a href="Thread-Safety.md#C++-Standard-Thread-Safety-For-Envelope-Letter-Internally-Synchronized">C++-Standard-Thread-Safety-For-Envelope-Letter-Internally-Synchronized</a> as well.
75 */
76 class Route {
77 public:
78 /**
79 * Any route to apply the handler, must match ALL argument constraints.
80 * If verbMatch is omitted, it it assumed to be IO::Network::HTTP::MethodsRegEx::kGet (NOT RegularExpression::kAny)
81 *
82 * \note that the request handler is called with any String arguments based on the pathMatch regular expression.
83 *
84 * \note that Routes that match on a hostRelativeURI, the hostRelativeURI is first normalized (funny characters translated to unicode, sequences of // removed etc)
85 * before matching against the regular expression. This means that it is always a mistake for a route URI match to begin with a slash, as it will
86 * never match.
87 *
88 * \par Example Usage (GET with explicit method regexp)
89 * \code
90 * Route{HTTP::MethodsRegEx::kGet, "session(/?)"_RegEx, [this] (Message& m) {
91 * WriteResponse (m.rwResponse (), kSession_, kMapper.FromObject (fWSImpl_->Session_GET ()));
92 * }},
93 * \endcode
94 *
95 * \par Example Usage (GET with defaulted GET method spec, and no arguments)
96 * \code
97 * Route{"session(/?)"_RegEx, [this] (Message& m) {
98 * WriteResponse (m.rwResponse (), kSession_, kMapper.FromObject (fWSImpl_->Session_GET ()));
99 * }},
100 * \endcode
101 *
102 * \par Example Usage (GET with arg parsed from URL path)
103 * \code
104 * Route{"resource/(.+)"_RegEx, [this] (Message& m, const String& resID) {
105 * auto r = fWSImpl_->resource_GET (resID);
106 * m.rwResponse().contentType = get<InternetMediaType> (r);
107 * m.rwResponse().write (get<BLOB> (r));
108 * }},
109 * \endcode
110 *
111 * \par Example Usage (POST, and grab params from Body)
112 * \code
113 * Route{HTTP::MethodsRegEx::kPost, "HR(/?)"_RegEx, [this] (Message& m) {
114 * if (optional<InternetMediaType> ct = m->request ().contentType()) {
115 * WriteResponse (m.rwResponse (), kHR_, kMapper.FromObject (fWSImpl_->HR_POST (*ct, m->rwRequest().GetBody ())));
116 * }
117 * else {
118 * Execution::Throw (Execution::RuntimeErrorException{"expected Content-Type HTTP Request header"});
119 * }
120 * }},
121 * \endcode
122 *
123 */
124 Route (const RegularExpression& verbMatch, const RegularExpression& pathMatch, const RequestHandler& handler);
125 Route (const RegularExpression& pathMatch, const RequestHandler& handler);
126 Route (const function<bool (const String& method, const String& hostRelPath, const Request& request)>& requestMatcher,
127 const RequestHandler& handler);
128
129 public:
130 /**
131 * Check if the given request matches this Route.
132 * Overload taking method/hostRelPath can be derived from request, but can be substituted with different values.
133 * Overload with optional matches returns variable matches in the regexp for the path
134 *
135 * We interpret routes as matching against a relative path from the root
136 */
137 nonvirtual bool Matches (const Request& request, Sequence<String>* pathRegExpMatches = nullptr) const;
138 nonvirtual bool Matches (const String& method, const String& hostRelPath, const Request& request,
139 Sequence<String>* pathRegExpMatches = nullptr) const;
140
141 private:
142 // @todo instead of two optionals - this should be 'variant'
143 optional<pair<RegularExpression, RegularExpression>> fVerbAndPathMatch_;
144 optional<function<bool (const String& method, const String& hostRelPath, const Request& request)>> fRequestMatch_;
145 RequestHandler fHandler_;
146
147 private:
148 friend class Router;
149 };
150
151 /**
152 * If there is not an EXPLICIT route matched for HEAD, or OPTIONS: those methods are implemented
153 * automatically by the Router.
154 *
155 * \note \em Thread-Safety <a href="Thread-Safety.md#C++-Standard-Thread-Safety-For-Envelope-Letter-Internally-Synchronized">C++-Standard-Thread-Safety-For-Envelope-Letter-Internally-Synchronized</a>
156 * But note that HandleMessage() is a const method, so it can safely be called from any number of threads
157 * simultaneously.
158 */
159 class Router : public Interceptor {
160 private:
161 using inherited = Interceptor;
162
163 public:
164 /**
165 */
166 Router () = delete;
167 Router (Router&&) noexcept = default;
168 Router (const Router&) noexcept = default;
169 Router (const Sequence<Route>& routes, const CORSOptions& corsOptions);
170
171 public:
172 nonvirtual Router& operator= (Router&&) noexcept = default;
173 nonvirtual Router& operator= (const Router&) = default;
174
175 public:
176 /**
177 * typically just examine host-relative part of URL, routes CAN examine any part of the request;
178 * typically returns just one handler (and matched strings), but can return multiple.
179 *
180 * \note before Stroika v3.0d12 - this returned optional<RequestHandler>, but now returns Iterator<RequestHandler> - all matching routes (but lazy evaluated).
181 */
182 nonvirtual Iterator<tuple<RequestHandler, Sequence<String>>> Lookup (const Request& request) const;
183
184 private:
185 struct Rep_;
186 };
187
188}
189
190/*
191 ********************************************************************************
192 ***************************** Implementation Details ***************************
193 ********************************************************************************
194 */
195#include "Router.inl"
196
197#endif /*_Stroika_Framework_WebServer_Router_h_*/
RegularExpression is a compiled regular expression which can be used to match on a String class.
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
An Iterator<T> is a copyable object which allows traversing the contents of some container....
Definition Iterator.h:225
this represents a HTTP request object for the WebServer module
nonvirtual bool Matches(const Request &request, Sequence< String > *pathRegExpMatches=nullptr) const
Definition Router.cpp:54
nonvirtual Iterator< tuple< RequestHandler, Sequence< String > > > Lookup(const Request &request) const
Definition Router.cpp:297