4#include "Stroika/Frameworks/StroikaPreComp.h"
8#include "Stroika/Foundation/Execution/Exceptions.h"
9#include "Stroika/Foundation/IO/Network/HTTP/ClientErrorException.h"
10#include "Stroika/Foundation/IO/Network/HTTP/Headers.h"
11#include "Stroika/Foundation/IO/Network/HTTP/Methods.h"
21using namespace Stroika::Foundation::Memory;
24using namespace Stroika::Frameworks;
28using Memory::MakeSharedPtr;
34 String ExtractHostRelPath_ (
const URI& url)
40 DbgTrace (
"ExtractHostRelPath_('{}') normalized URL path to '{}'"_f, url, normal);
45 static const auto kException_ =
ClientErrorException{HTTP::StatusCodes::kBadRequest,
"request URI requires an absolute path"sv};
58 return Matches (request.
httpMethod, ExtractHostRelPath_ (request.
url ()), request, pathRegExpMatches);
62 if (fVerbAndPathMatch_) {
63 if (not method.
Matches (fVerbAndPathMatch_->first)) {
66 return (pathRegExpMatches ==
nullptr) ? hostRelPath.
Matches (fVerbAndPathMatch_->second)
67 : hostRelPath.
Matches (fVerbAndPathMatch_->second, pathRegExpMatches);
69 else if (fRequestMatch_) {
70 return (*fRequestMatch_) (method, hostRelPath, request);
84 static const optional<Set<String>> MapStartToNullOpt_ (
const optional<
Set<String>>& o)
87 optional<Set<String>> m = o;
88 if (m and m->Contains (CORSOptions::kAccessControlWildcard)) {
98 : fAllowedOrigins_{MapStartToNullOpt_ (filledInCORSOptions.fAllowedOrigins)}
99 , fAllowedHeaders_{MapStartToNullOpt_ (filledInCORSOptions.fAllowedHeaders)}
100 , fAccessControlAllowCredentialsValue_{*filledInCORSOptions.fAllowCredentials ?
"true"sv :
"false"sv}
101 , fAccessControlMaxAgeValue_{
"{}"_f(*filledInCORSOptions.fAccessControlMaxAge)}
105 virtual void HandleMessage (
Message& m)
const override
107#if USE_NOISY_TRACE_IN_THIS_MODULE_
111 if (Handle_Via_RequestHandler_ (m, get<
Sequence<String>> (*handlerI), get<RequestHandler> (*handlerI))) {
115 if (m.
request ().httpMethod () == HTTP::Methods::kHead and Handle_HEAD_ (m)) {
118 else if (m.
request ().httpMethod () == HTTP::Methods::kOptions) {
122 if (optional<
Set<String>> o = GetAllowedMethodsForRequest_ (m.
request ()); o && not o->Contains (m.
request ().httpMethod ())) {
126 Assert (not o->empty ());
136 nonvirtual
void HandleCORSInNormallyHandledMessage_ (
const Request& request, Response& response)
const
146 optional<String> allowedOrigin;
147 if (
auto origin = request.
headers ().origin ()) {
148 if (fAllowedOrigins_.has_value ()) {
151 String originStr = origin->ToString ();
152 if (fAllowedOrigins_->Contains (originStr)) {
153 allowedOrigin = originStr;
157 allowedOrigin = CORSOptions::kAccessControlWildcard;
161 response.rwHeaders ().accessControlAllowOrigin = allowedOrigin;
162 if (fAllowedOrigins_) {
165 response.rwHeaders ().vary = Memory::NullCoalesce (response.headers ().vary ()) +
String{HTTP::HeaderName::kOrigin};
171 return Traversal::CreateGeneratorIterator<tuple<RequestHandler, Sequence<String>>> (
175 if (cur->Matches (method, hostRelPath, request, &matches)) {
176 auto result = make_tuple (cur->fHandler_, matches);
189 return Lookup_ (request.
httpMethod, ExtractHostRelPath_ (request.
url), request);
191 nonvirtual optional<Set<String>> GetAllowedMethodsForRequest_ (
const Request& request)
const
193 String hostRelPath = ExtractHostRelPath_ (request.
url);
194 static const Set<String> kMethods2Try_{HTTP::Methods::kGet, HTTP::Methods::kPut, HTTP::Methods::kOptions,
195 HTTP::Methods::kDelete, HTTP::Methods::kPost, HTTP::Methods::kPatch};
197 for (
const String& method : kMethods2Try_) {
198 for (
const Route& r : fRoutes_) {
199 if (r.Matches (method, hostRelPath, request)) {
200 methods.
Add (method);
204 return methods.
empty () ? nullopt : optional<Set<String>>{methods};
210 HandleCORSInNormallyHandledMessage_ (request, response);
211 bool handled =
false;
212 (handler) (message, matches, handled);
215 nonvirtual
bool Handle_HEAD_ (
Message& message)
const
221 handlerEtc; ++handlerEtc) {
223 response.headMode =
true;
224 HandleCORSInNormallyHandledMessage_ (request, response);
225 bool handled =
false;
226 get<RequestHandler> (*handlerEtc) (message, get<Sequence<String>> (*handlerEtc), handled);
227 if (handled) [[likely]] {
233 nonvirtual
void Handle_OPTIONS_ (
Message& message)
const
238 auto o = GetAllowedMethodsForRequest_ (request);
241 auto& responseHeaders = response.rwHeaders ();
242 responseHeaders.Set (HTTP::HeaderName::kAccessControlAllowCredentials, fAccessControlAllowCredentialsValue_);
243 if (
auto accessControlRequestHeaders = request.
headers ().LookupOne (HTTP::HeaderName::kAccessControlRequestHeaders)) {
244 if (fAllowedHeaders_) {
246 Iterable<String> requestAccessHeaders = accessControlRequestHeaders->Tokenize ({
','});
247 auto r = fAllowedHeaders_->Intersection (requestAccessHeaders);
249 responseHeaders.Set (HTTP::HeaderName::kAccessControlAllowHeaders,
String::Join (r));
253 responseHeaders.Set (HTTP::HeaderName::kAccessControlAllowHeaders, *accessControlRequestHeaders);
256 responseHeaders.Set (HTTP::HeaderName::kAccessControlAllowMethods,
String::Join (*o));
257 responseHeaders.Set (HTTP::HeaderName::kAccessControlMaxAge, fAccessControlMaxAgeValue_);
259 HandleCORSInNormallyHandledMessage_ (request, response);
260 response.status = HTTP::StatusCodes::kNoContent;
263 DbgTrace (
"Router 404: (...url={})"_f, request.
url ());
269 return "Router ({} routes)"_f(fRoutes_.size ());
272 const optional<Set<String>> fAllowedOrigins_;
273 const optional<Set<String>> fAllowedHeaders_;
274 const String fAccessControlAllowCredentialsValue_;
275 const String fAccessControlMaxAgeValue_;
295 : inherited{
MakeSharedPtr<Rep_> (routes, FillIn_ (corsOptions))}
301 return _GetRep<Rep_> ().Lookup_ (request);
#define AssertNotReached()
auto MakeSharedPtr(ARGS_TYPE &&... args) -> shared_ptr< T >
same as make_shared, but if type T has block allocation, then use block allocation for the 'shared pa...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual bool Matches(const RegularExpression ®Ex) const
static String Join(const Iterable< String > &list, const String &separator=", "sv)
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
nonvirtual void Add(ArgByValueType< value_type > item)
ClientErrorException is to capture exceptions caused by a bad (e.g ill-formed) request.
Common::Property< String > httpMethod
typically HTTP::Methods::kGet
Common::ReadOnlyProperty< const Headers & > headers
Common::Property< URI > url
nonvirtual RETURN_VALUE GetAbsPath() const
Return the GetPath () value, but assuring its an absolute path.
nonvirtual URI Normalize(NormalizationStyle normalization=NormalizationStyle::eDefault) const
Produce a normalized representation of the URI.
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
nonvirtual bool empty() const
Returns true iff size() == 0.
An Iterator<T> is a copyable object which allows traversing the contents of some container....
Common::ReadOnlyProperty< Response & > rwResponse
Common::ReadOnlyProperty< const Request & > request
this represents a HTTP request object for the WebServer module
nonvirtual bool Matches(const Request &request, Sequence< String > *pathRegExpMatches=nullptr) const
nonvirtual Iterator< tuple< RequestHandler, Sequence< String > > > Lookup(const Request &request) const
STRING_TYPE ToString(FLOAT_TYPE f, const ToStringOptions &options={})
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
constexpr auto kHeaderNameEqualsComparer
optional< bool > fAllowCredentials
optional< unsigned int > fAccessControlMaxAge
optional< Set< String > > fAllowedOrigins
optional< Set< String > > fAllowedHeaders