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"
19using namespace Stroika::Foundation::IO;
22using namespace Stroika::Foundation::Time;
31 struct DefaultCacheRep_ : Transfer::Cache::IRep {
35 using DefaultOptions = Transfer::Cache::DefaultOptions;
37 struct MyElement_ : Element {
38 MyElement_ () =
default;
39 MyElement_ (
const Response& response)
44 virtual optional<DateTime> IsValidUntil ()
const override
49 if (fExpiresDefault) {
50 return *fExpiresDefault;
58 sb <<
", ExpiresDefault: "sv << fExpiresDefault;
62 optional<Time::DateTime> fExpiresDefault;
65 DefaultCacheRep_ (
const DefaultOptions& options)
67 , fCache_{options.fCacheSize.value_or (101), 11u}
71 virtual optional<Response> OnBeforeFetch (EvalContext* context,
const URI& schemeAndAuthority,
Request* request)
noexcept override
73#if USE_NOISY_TRACE_IN_THIS_MODULE_
75 "IO::Network::Transfer ... {}::DefaultCacheRep_::OnBeforeFetch",
"schemeAndAuthority={}"_f, schemeAndAuthority)};
77 if (request->fMethod == HTTP::Methods::kGet) {
79 URI fullURI = schemeAndAuthority.
Combine (request->fAuthorityRelativeURL);
80 context->fFullURI = fullURI;
81 if (optional<MyElement_> o = fCache_.Lookup (fullURI)) {
83 DateTime now = DateTime::Now ();
84 bool canReturnDirectly = o->IsValidUntil ().value_or (now) > now;
85 if (canReturnDirectly) {
88 if (fOptions_.fCachedResultHeader) {
89 headers.
Add (*fOptions_.fCachedResultHeader,
String{});
91 return Response{o->fBody, HTTP::StatusCodes::kOK, headers};
96 bool canCheckCacheETAG = o->fETag.has_value ();
97 if (canCheckCacheETAG) {
98 context->fCachedElement = *o;
99 request->fOverrideHeaders.
Add (HTTP::HeaderName::kIfNoneMatch,
"\""sv + *o->fETag +
"\""sv);
102 bool canCheckModifiedSince = o->fLastModified.has_value ();
103 if (canCheckModifiedSince) {
104 context->fCachedElement = *o;
105 request->fOverrideHeaders.Add (HTTP::HeaderName::kIfModifiedSince,
106 "\""sv + o->fLastModified->Format (DateTime::kRFC1123Format) +
"\""sv);
111 DbgTrace (
"Cache::OnBeforeFetch::oops: {}"_f, current_exception ());
118 virtual void OnAfterFetch (
const EvalContext& context,
Response* response)
noexcept override
120#if USE_NOISY_TRACE_IN_THIS_MODULE_
124 switch (response->GetStatus ()) {
125 case HTTP::StatusCodes::kOK: {
126 if (context.fFullURI) {
128 MyElement_ cacheElement{*response};
129 if (fOptions_.fDefaultResourceTTL) {
130 cacheElement.fExpiresDefault = DateTime::Now () + *fOptions_.fDefaultResourceTTL;
132 if (cacheElement.IsCachable ()) {
134 fCache_.Add (*context.fFullURI, cacheElement);
138 DbgTrace (
"Cache::OnAfterFetch::oops(ok): {}"_f, current_exception ());
142 case HTTP::StatusCodes::kNotModified: {
149 if (context.fCachedElement) {
151 if (fOptions_.fCachedResultHeader) {
152 headers.
Add (*fOptions_.fCachedResultHeader,
String{});
154 *response =
Response{context.fCachedElement->fBody, HTTP::StatusCodes::kOK, headers, response->GetSSLResultInfo ()};
157 DbgTrace (
"Cache::OnAfterFetch::oops: unexpected NOT-MODIFIED result when nothing was in the cache"_f);
161 DbgTrace (
"Cache::OnAfterFetch::oops(ok): {}"_f, current_exception ());
170 virtual void ClearCache ()
override
175 virtual optional<Element> Lookup (
const URI& url)
const override
177 return fCache_.Lookup (url);
180 DefaultOptions fOptions_;
192Transfer::Cache::Element::Element (
const Response& response)
193 : fBody{response.GetData ()}
196 for (
auto hi = headers.
begin (); hi != headers.
end ();) {
205 if (hi->fKey == HTTP::HeaderName::kETag) {
206 if (hi->fValue.size () < 2 or not hi->fValue.StartsWith (
"\""sv) or not hi->fValue.EndsWith (
"\""sv)) {
209 fETag = hi->fValue.SubString (1, -1);
210 hi = headers.
erase (hi);
212 else if (hi->fKey == HTTP::HeaderName::kExpires) {
214 fExpires = DateTime::Parse (hi->fValue, DateTime::kRFC1123Format);
219 fExpires = DateTime::Now ();
220 DbgTrace (
"Malformed expires ({}) treated as expires immediately"_f, hi->fValue);
222 hi = headers.
erase (hi);
224 else if (hi->fKey == HTTP::HeaderName::kLastModified) {
226 fLastModified = DateTime::Parse (hi->fValue, DateTime::kRFC1123Format);
229 DbgTrace (
"Malformed last-modified ({}) treated as ignored"_f, hi->fValue);
231 hi = headers.
erase (hi);
233 else if (hi->fKey == HTTP::HeaderName::kCacheControl) {
234 fCacheControl =
Set<String>{hi->fValue.Tokenize ({
','})};
235 hi = headers.
erase (hi);
236 static const String kMaxAgeEquals_{
"max-age="sv};
237 for (
const String& cci : *fCacheControl) {
238 if (cci.StartsWith (kMaxAgeEquals_)) {
243 else if (hi->fKey == HTTP::HeaderName::kContentType) {
245 hi = headers.
erase (hi);
251 fOtherHeaders = headers;
258 result.
Add (HTTP::HeaderName::kETag,
"\""sv + *fETag +
"\""sv);
261 result.
Add (HTTP::HeaderName::kExpires, fExpires->Format (DateTime::kRFC1123Format));
264 result.
Add (HTTP::HeaderName::kLastModified, fLastModified->Format (DateTime::kRFC1123Format));
268 return lhs.empty () ? rhs : (lhs +
","sv + rhs);
270 result.
Add (HTTP::HeaderName::kCacheControl, fCacheControl->Reduce (a).value_or (
String{}));
273 result.
Add (HTTP::HeaderName::kContentType, fContentType->As<
String> ());
280 static const String kNoStore_{
"no-store"sv};
282 return not fCacheControl->
Contains (kNoStore_);
292 if (fExpiresDueToMaxAge) {
293 return *fExpiresDueToMaxAge;
295 static const String kNoCache_{
"no-cache"sv};
296 if (fCacheControl and fCacheControl->Contains (kNoCache_)) {
297 return DateTime::Now ().AddSeconds (-1);
306 sb <<
", ETag: "sv << fETag;
307 sb <<
", Expires: "sv << fExpires;
308 sb <<
", ExpiresDueToMaxAge: "sv << fExpiresDueToMaxAge;
309 sb <<
", LastModified: "sv << fLastModified;
310 sb <<
", CacheControl: "sv << fCacheControl;
311 sb <<
", ContentType: "sv << fContentType;
312 sb <<
", OtherHeaders: "sv << fOtherHeaders;
313 sb <<
", Body: "sv << fBody;
329 return Ptr{Memory::MakeSharedPtr<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...
virtual bool IsCachable() const
nonvirtual String ToString() const
virtual optional< Time::DateTime > IsValidUntil() const