Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
FileSystemRequestHandler.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
10#include "Stroika/Foundation/IO/FileSystem/Common.h"
12#include "Stroika/Foundation/IO/Network/HTTP/ClientErrorException.h"
14
15#include "FileSystemRequestHandler.h"
16
17using std::byte;
18
19using namespace Stroika::Foundation;
23using namespace Stroika::Foundation::Execution;
24using namespace Stroika::Foundation::Memory;
25using namespace Stroika::Foundation::Streams;
26
27using namespace Stroika::Frameworks;
28using namespace Stroika::Frameworks::WebServer;
29
31
32// Comment this in to turn on aggressive noisy DbgTrace in this module
33// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
34
35namespace {
36 struct FSRouterRep_ {
37 filesystem::path fFSRoot_;
38 String fURLPrefix2Strip_;
39 Sequence<filesystem::path> fDefaultIndexFileNames;
40 vector<pair<RegularExpression, CacheControl>> fCacheControlSettings;
41 optional<filesystem::path> fFallbackFile_;
42
43 FSRouterRep_ (const filesystem::path& filesystemRoot, const optional<String>& urlPrefix2Strip, const Sequence<filesystem::path>& defaultIndexFileNames,
44 const optional<Sequence<pair<RegularExpression, CacheControl>>>& cacheControlSettings, const optional<filesystem::path>& fallbackFile)
45 : fFSRoot_{filesystem::canonical (filesystemRoot)}
46 , fURLPrefix2Strip_{urlPrefix2Strip.value_or ("/"sv)}
47 , fDefaultIndexFileNames{defaultIndexFileNames}
48 , fFallbackFile_{fallbackFile}
49 {
50 if (cacheControlSettings) {
51 for (const auto& i : *cacheControlSettings) {
52 fCacheControlSettings.push_back (i);
53 }
54 }
55 }
56 void HandleMessage (Message& m, bool& handled)
57 {
58#if qStroika_Foundation_Debug_DefaultTracingOn
59 Debug::TimingTrace ttrc{"FSRouterRep_::HandleMessage", 1ms}; // prelim - gather info on whether worth supporting ETAGs etc - why is this sometimes somewhat slow
60#endif
61
62 /*
63 * @todo rewrite to incrementally copy file from stream, not read all into RAM
64 */
66 if (optional<String> urlHostRelPath = ExtractURLHostRelPath_ (m)) {
67 filesystem::path fn{fFSRoot_ / urlHostRelPath->As<filesystem::path> ()};
68 if (fFallbackFile_ and not filesystem::exists (fn)) {
69 fn = fFSRoot_ / *fFallbackFile_;
70 }
71
72#if USE_NOISY_TRACE_IN_THIS_MODULE_
74 Stroika_Foundation_Debug_OptionalizeTraceArgs ("...FileSystemRequestHandler...HandleMessage", "relURL={}, serving fn={}"_f,
75 m.request ().url ().GetAuthorityRelativeResource (), fn)};
76#endif
77 try {
78 Response& response = m.rwResponse ();
79 InputStream::Ptr<byte> in{FileInputStream::New (fn)};
80 if (optional<InternetMediaType> oMediaType = InternetMediaTypeRegistry::sThe->GetAssociatedContentType (fn.extension ())) {
81 response.contentType = *oMediaType;
82#if USE_NOISY_TRACE_IN_THIS_MODULE_
83 DbgTrace ("content-type: {}"_f, *oMediaType);
84#endif
85 }
86 ApplyCacheControl_ (response, *urlHostRelPath);
87 response.write (in.ReadAll ());
88 handled = true;
89 }
90 catch (const system_error& e) {
91 if (e.code () == errc::no_such_file_or_directory) {
92 Assert (not handled);
93 handled = false; // Router itself will issue Throw (ClientErrorException{HTTP::StatusCodes::kNotFound});
94 }
95 else {
96 ReThrow ();
97 }
98 }
99 }
100 }
101 void ApplyCacheControl_ (Response& r, const String& urlRelPath) const
102 {
103 for (const auto& i : fCacheControlSettings) {
104 if (urlRelPath.Matches (i.first)) {
105 r.rwHeaders ().cacheControl = i.second;
106 break; // just apply first
107 }
108 }
109 }
110 optional<String> ExtractURLHostRelPath_ (const Message& m) const
111 {
112 const Request& request = m.request ();
113 String urlHostRelPath = request.url ().Normalize (URI::NormalizationStyle::eAggressive).GetAbsPath<String> ();
114 Assert (not urlHostRelPath.Contains ("/../")); // so no escape magic - normalize assures
115 if (not fURLPrefix2Strip_.empty ()) {
116 if (urlHostRelPath.StartsWith (fURLPrefix2Strip_)) {
117 urlHostRelPath = urlHostRelPath.SubString (fURLPrefix2Strip_.length ());
118 if (urlHostRelPath.StartsWith ("/"sv)) {
119 urlHostRelPath = urlHostRelPath.SubString (1);
120 }
121 }
122 else {
123 return nullopt;
124 }
125 }
126 if ((urlHostRelPath.empty () or urlHostRelPath.EndsWith ('/')) and not fDefaultIndexFileNames.empty ()) {
127 //@todo tmphack - need to try a bunch and look for 'access'
128 urlHostRelPath = urlHostRelPath + String{fDefaultIndexFileNames[0]};
129 }
130 return urlHostRelPath;
131 }
132 };
133}
134
135/*
136 ********************************************************************************
137 *********************** WebServer::FileSystemRequestHandler ********************
138 ********************************************************************************
139 */
140FileSystemRequestHandler::FileSystemRequestHandler (const filesystem::path& filesystemRoot, const Options& options)
141 : RequestHandler{[rep = make_shared<FSRouterRep_> (filesystemRoot, options.fURLPrefix2Strip, Memory::NullCoalesce (options.fDefaultIndexFileNames),
142 options.fCacheControlSettings, options.fFallbackFile)] (
143 Message& m, const Sequence<String>&, bool& handled) -> void { rep->HandleMessage (m, handled); }}
144{
145 DbgTrace ("fDefaultIndexFileNames={}"_f, options.fDefaultIndexFileNames);
146}
const OT & NullCoalesce(const OT &l, const OT &r)
return one of l, or r, with first preference for which is engaged, and second preference for left-to-...
Definition Optional.inl:134
#define DbgTrace
Definition Trace.h:309
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual bool Contains(Character c, CompareOptions co=eWithCase) const
Definition String.inl:693
nonvirtual size_t length() const noexcept
Definition String.inl:1045
nonvirtual bool Matches(const RegularExpression &regEx) const
Definition String.cpp:1133
nonvirtual bool EndsWith(const Character &c, CompareOptions co=eWithCase) const
Definition String.cpp:1088
nonvirtual String SubString(SZ from) const
nonvirtual bool StartsWith(const Character &c, CompareOptions co=eWithCase) const
Definition String.cpp:1059
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
nonvirtual void push_back(ArgByValueType< value_type > item)
Definition Sequence.inl:436
ClientErrorException is to capture exceptions caused by a bad (e.g ill-formed) request.
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual bool empty() const
Returns true iff size() == 0.
Definition Iterable.inl:306
FileSystemRequestHandler(const filesystem::path &filesystemRoot, const Options &options=kDefaultOptions)
Common::ReadOnlyProperty< Response & > rwResponse
Definition Message.h:93
Common::ReadOnlyProperty< const Request & > request
Definition Message.h:72
this represents a HTTP request object for the WebServer module
Create a format-string (see std::wformat_string or Stroika FormatString, or python 'f' strings.
ConnectionManager::Options specify things like default headers, caching policies, binding flags (not ...