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"
20using namespace Stroika::Foundation::Memory;
23using namespace Stroika::Frameworks;
32 String ExtractHostRelPath_ (
const URI& url)
38 DbgTrace (
"ExtractHostRelPath_('{}') normalized URL path to '{}'"_f, url, normal);
43 static const auto kException_ =
ClientErrorException{HTTP::StatusCodes::kBadRequest,
"request URI requires an absolute path"sv};
56 return Matches (request.
httpMethod, ExtractHostRelPath_ (request.
url ()), request, pathRegExpMatches);
60 if (fVerbAndPathMatch_) {
61 if (not method.
Matches (fVerbAndPathMatch_->first)) {
64 return (pathRegExpMatches ==
nullptr) ? hostRelPath.
Matches (fVerbAndPathMatch_->second)
65 : hostRelPath.
Matches (fVerbAndPathMatch_->second, pathRegExpMatches);
67 else if (fRequestMatch_) {
68 return (*fRequestMatch_) (method, hostRelPath, request);
82 static const optional<Set<String>> MapStartToNullOpt_ (
const optional<
Set<String>>& o)
85 optional<Set<String>> m = o;
86 if (m and m->Contains (CORSOptions::kAccessControlWildcard)) {
96 : fAllowedOrigins_{MapStartToNullOpt_ (filledInCORSOptions.fAllowedOrigins)}
97 , fAllowedHeaders_{MapStartToNullOpt_ (filledInCORSOptions.fAllowedHeaders)}
98 , fAccessControlAllowCredentialsValue_{*filledInCORSOptions.fAllowCredentials ?
"true"sv :
"false"sv}
99 , fAccessControlMaxAgeValue_{
"{}"_f(*filledInCORSOptions.fAccessControlMaxAge)}
103 virtual void HandleMessage (
Message& m)
const override
105#if USE_NOISY_TRACE_IN_THIS_MODULE_
109 if (Handle_Via_RequestHandler_ (m, get<
Sequence<String>> (*handlerI), get<RequestHandler> (*handlerI))) {
113 if (m.
request ().httpMethod () == HTTP::Methods::kHead and Handle_HEAD_ (m)) {
116 else if (m.
request ().httpMethod () == HTTP::Methods::kOptions) {
120 if (optional<
Set<String>> o = GetAllowedMethodsForRequest_ (m.
request ()); o && not o->Contains (m.
request ().httpMethod ())) {
124 Assert (not o->empty ());
134 nonvirtual
void HandleCORSInNormallyHandledMessage_ (
const Request& request, Response& response)
const
144 optional<String> allowedOrigin;
145 if (
auto origin = request.
headers ().origin ()) {
146 if (fAllowedOrigins_.has_value ()) {
149 String originStr = origin->ToString ();
150 if (fAllowedOrigins_->Contains (originStr)) {
151 allowedOrigin = originStr;
155 allowedOrigin = CORSOptions::kAccessControlWildcard;
159 response.rwHeaders ().accessControlAllowOrigin = allowedOrigin;
160 if (fAllowedOrigins_) {
163 response.rwHeaders ().vary = Memory::NullCoalesce (response.headers ().vary ()) +
String{HTTP::HeaderName::kOrigin};
169 return Traversal::CreateGeneratorIterator<tuple<RequestHandler, Sequence<String>>> (
173 if (cur->Matches (method, hostRelPath, request, &matches)) {
174 auto result = make_tuple (cur->fHandler_, matches);
187 return Lookup_ (request.
httpMethod, ExtractHostRelPath_ (request.
url), request);
189 nonvirtual optional<Set<String>> GetAllowedMethodsForRequest_ (
const Request& request)
const
191 String hostRelPath = ExtractHostRelPath_ (request.
url);
192 static const Set<String> kMethods2Try_{HTTP::Methods::kGet, HTTP::Methods::kPut, HTTP::Methods::kOptions,
193 HTTP::Methods::kDelete, HTTP::Methods::kPost, HTTP::Methods::kPatch};
195 for (
const String& method : kMethods2Try_) {
196 for (
const Route& r : fRoutes_) {
197 if (r.Matches (method, hostRelPath, request)) {
198 methods.
Add (method);
202 return methods.
empty () ? nullopt : optional<Set<String>>{methods};
208 HandleCORSInNormallyHandledMessage_ (request, response);
209 bool handled =
false;
210 (handler) (message, matches, handled);
213 nonvirtual
bool Handle_HEAD_ (
Message& message)
const
219 handlerEtc; ++handlerEtc) {
221 response.headMode =
true;
222 HandleCORSInNormallyHandledMessage_ (request, response);
223 bool handled =
false;
224 get<RequestHandler> (*handlerEtc) (message, get<Sequence<String>> (*handlerEtc), handled);
225 if (handled) [[likely]] {
231 nonvirtual
void Handle_OPTIONS_ (
Message& message)
const
236 auto o = GetAllowedMethodsForRequest_ (request);
239 auto& responseHeaders = response.rwHeaders ();
240 responseHeaders.Set (HTTP::HeaderName::kAccessControlAllowCredentials, fAccessControlAllowCredentialsValue_);
241 if (
auto accessControlRequestHeaders = request.
headers ().LookupOne (HTTP::HeaderName::kAccessControlRequestHeaders)) {
242 if (fAllowedHeaders_) {
244 Iterable<String> requestAccessHeaders = accessControlRequestHeaders->Tokenize ({
','});
245 auto r = fAllowedHeaders_->Intersection (requestAccessHeaders);
247 responseHeaders.Set (HTTP::HeaderName::kAccessControlAllowHeaders,
String::Join (r));
251 responseHeaders.Set (HTTP::HeaderName::kAccessControlAllowHeaders, *accessControlRequestHeaders);
254 responseHeaders.Set (HTTP::HeaderName::kAccessControlAllowMethods,
String::Join (*o));
255 responseHeaders.Set (HTTP::HeaderName::kAccessControlMaxAge, fAccessControlMaxAgeValue_);
257 HandleCORSInNormallyHandledMessage_ (request, response);
258 response.status = HTTP::StatusCodes::kNoContent;
261 DbgTrace (
"Router 404: (...url={})"_f, request.
url ());
267 return "Router ({} routes)"_f(fRoutes_.size ());
270 const optional<Set<String>> fAllowedOrigins_;
271 const optional<Set<String>> fAllowedHeaders_;
272 const String fAccessControlAllowCredentialsValue_;
273 const String fAccessControlMaxAgeValue_;
293 : inherited{make_shared<Rep_> (routes, FillIn_ (corsOptions))}
299 return _GetRep<Rep_> ().Lookup_ (request);
#define AssertNotReached()
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