4#include "Stroika/Foundation/StroikaPreComp.h"
6#include "Stroika/Foundation/Cache/SynchronizedLRUCache.h"
9#include "Stroika/Foundation/IO/Network/HTTP/Headers.h"
10#include "Stroika/Foundation/IO/Network/HTTP/Methods.h"
18using namespace Stroika::Foundation::IO;
21using namespace Stroika::Foundation::Time;
30 struct DefaultCacheRep_ : Transfer::Cache::IRep {
34 using DefaultOptions = Transfer::Cache::DefaultOptions;
36 struct MyElement_ : Element {
37 MyElement_ () =
default;
38 MyElement_ (
const Response& response)
43 virtual optional<DateTime> IsValidUntil ()
const override
48 if (fExpiresDefault) {
49 return *fExpiresDefault;
57 sb <<
", ExpiresDefault: "sv << fExpiresDefault;
61 optional<Time::DateTime> fExpiresDefault;
64 DefaultCacheRep_ (
const DefaultOptions& options)
66 , fCache_{options.fCacheSize.value_or (101), 11u}
70 virtual optional<Response> OnBeforeFetch (EvalContext* context,
const URI& schemeAndAuthority,
Request* request)
noexcept override
72#if USE_NOISY_TRACE_IN_THIS_MODULE_
74 "IO::Network::Transfer ... {}::DefaultCacheRep_::OnBeforeFetch",
"schemeAndAuthority={}"_f, schemeAndAuthority)};
76 if (request->fMethod == HTTP::Methods::kGet) {
78 URI fullURI = schemeAndAuthority.
Combine (request->fAuthorityRelativeURL);
79 context->fFullURI = fullURI;
80 if (optional<MyElement_> o = fCache_.Lookup (fullURI)) {
82 DateTime now = DateTime::Now ();
83 bool canReturnDirectly = o->IsValidUntil ().value_or (now) > now;
84 if (canReturnDirectly) {
87 if (fOptions_.fCachedResultHeader) {
88 headers.
Add (*fOptions_.fCachedResultHeader,
String{});
90 return Response{o->fBody, HTTP::StatusCodes::kOK, headers};
95 bool canCheckCacheETAG = o->fETag.has_value ();
96 if (canCheckCacheETAG) {
97 context->fCachedElement = *o;
98 request->fOverrideHeaders.
Add (HTTP::HeaderName::kIfNoneMatch,
"\""sv + *o->fETag +
"\""sv);
101 bool canCheckModifiedSince = o->fLastModified.has_value ();
102 if (canCheckModifiedSince) {
103 context->fCachedElement = *o;
104 request->fOverrideHeaders.Add (HTTP::HeaderName::kIfModifiedSince,
105 "\""sv + o->fLastModified->Format (DateTime::kRFC1123Format) +
"\""sv);
110 DbgTrace (
"Cache::OnBeforeFetch::oops: {}"_f, current_exception ());
117 virtual void OnAfterFetch (
const EvalContext& context,
Response* response)
noexcept override
119#if USE_NOISY_TRACE_IN_THIS_MODULE_
123 switch (response->GetStatus ()) {
124 case HTTP::StatusCodes::kOK: {
125 if (context.fFullURI) {
127 MyElement_ cacheElement{*response};
128 if (fOptions_.fDefaultResourceTTL) {
129 cacheElement.fExpiresDefault = DateTime::Now () + *fOptions_.fDefaultResourceTTL;
131 if (cacheElement.IsCachable ()) {
133 fCache_.Add (*context.fFullURI, cacheElement);
137 DbgTrace (
"Cache::OnAfterFetch::oops(ok): {}"_f, current_exception ());
141 case HTTP::StatusCodes::kNotModified: {
148 if (context.fCachedElement) {
150 if (fOptions_.fCachedResultHeader) {
151 headers.
Add (*fOptions_.fCachedResultHeader,
String{});
153 *response =
Response{context.fCachedElement->fBody, HTTP::StatusCodes::kOK, headers, response->GetSSLResultInfo ()};
156 DbgTrace (
"Cache::OnAfterFetch::oops: unexpected NOT-MODIFIED result when nothing was in the cache"_f);
160 DbgTrace (
"Cache::OnAfterFetch::oops(ok): {}"_f, current_exception ());
169 virtual void ClearCache ()
override
174 virtual optional<Element> Lookup (
const URI& url)
const override
176 return fCache_.Lookup (url);
179 DefaultOptions fOptions_;
191Transfer::Cache::Element::Element (
const Response& response)
192 : fBody{response.GetData ()}
195 for (
auto hi = headers.
begin (); hi != headers.
end ();) {
204 if (hi->fKey == HTTP::HeaderName::kETag) {
205 if (hi->fValue.size () < 2 or not hi->fValue.StartsWith (
"\""sv) or not hi->fValue.EndsWith (
"\""sv)) {
208 fETag = hi->fValue.SubString (1, -1);
209 hi = headers.
erase (hi);
211 else if (hi->fKey == HTTP::HeaderName::kExpires) {
213 fExpires = DateTime::Parse (hi->fValue, DateTime::kRFC1123Format);
218 fExpires = DateTime::Now ();
219 DbgTrace (
"Malformed expires ({}) treated as expires immediately"_f, hi->fValue);
221 hi = headers.
erase (hi);
223 else if (hi->fKey == HTTP::HeaderName::kLastModified) {
225 fLastModified = DateTime::Parse (hi->fValue, DateTime::kRFC1123Format);
228 DbgTrace (
"Malformed last-modified ({}) treated as ignored"_f, hi->fValue);
230 hi = headers.
erase (hi);
232 else if (hi->fKey == HTTP::HeaderName::kCacheControl) {
233 fCacheControl =
Set<String>{hi->fValue.Tokenize ({
','})};
234 hi = headers.
erase (hi);
235 static const String kMaxAgeEquals_{
"max-age="sv};
236 for (
const String& cci : *fCacheControl) {
237 if (cci.StartsWith (kMaxAgeEquals_)) {
242 else if (hi->fKey == HTTP::HeaderName::kContentType) {
244 hi = headers.
erase (hi);
250 fOtherHeaders = headers;
257 result.
Add (HTTP::HeaderName::kETag,
"\""sv + *fETag +
"\""sv);
260 result.
Add (HTTP::HeaderName::kExpires, fExpires->Format (DateTime::kRFC1123Format));
263 result.
Add (HTTP::HeaderName::kLastModified, fLastModified->Format (DateTime::kRFC1123Format));
267 return lhs.empty () ? rhs : (lhs +
","sv + rhs);
269 result.
Add (HTTP::HeaderName::kCacheControl, fCacheControl->Reduce (a).value_or (
String{}));
272 result.
Add (HTTP::HeaderName::kContentType, fContentType->As<
String> ());
279 static const String kNoStore_{
"no-store"sv};
281 return not fCacheControl->
Contains (kNoStore_);
291 if (fExpiresDueToMaxAge) {
292 return *fExpiresDueToMaxAge;
294 static const String kNoCache_{
"no-cache"sv};
295 if (fCacheControl and fCacheControl->Contains (kNoCache_)) {
296 return DateTime::Now ().AddSeconds (-1);
305 sb <<
", ETag: "sv << fETag;
306 sb <<
", Expires: "sv << fExpires;
307 sb <<
", ExpiresDueToMaxAge: "sv << fExpiresDueToMaxAge;
308 sb <<
", LastModified: "sv << fLastModified;
309 sb <<
", CacheControl: "sv << fCacheControl;
310 sb <<
", ContentType: "sv << fContentType;
311 sb <<
", OtherHeaders: "sv << fOtherHeaders;
312 sb <<
", Body: "sv << fBody;
328 return Ptr{make_shared<DefaultCacheRep_> (options)};
#define RequireNotNull(p)
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
simple wrapper on LRUCache (with the same API) - but internally synchronized in a way that is more pe...
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual bool Contains(Character c, CompareOptions co=eWithCase) const
nonvirtual String SubString(SZ from) const
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
nonvirtual void erase(ArgByValueType< key_type > key)
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
nonvirtual URI Combine(const URI &overridingURI) const
Combine overridingURI possibly relative url with this base url, to produce a new URI.
Duration is a chrono::duration<double> (=.
nonvirtual Iterator< T > begin() const
Support for ranged for, and STL syntax in general.
static constexpr default_sentinel_t end() noexcept
Support for ranged for, and STL syntax in general.
T ToFloat(span< const CHAR_T > s)
String ToString(T &&t, ARGS... args)
Return a debug-friendly, display version of the argument: not guaranteed parsable or usable except fo...
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
virtual bool IsCachable() const
nonvirtual String ToString() const
virtual optional< Time::DateTime > IsValidUntil() const