caching

Caching and streaming

Conditional GET

fresh-when sets validators on the response and renders a 304 Not Modified when the request's If-None-Match/If-Modified-Since show the client already has the current version. is-stale is its inverse: it returns true when you should render.

method show {
  self.render('show') if self.is-stale(etag => $post, last-modified => $post.updated-at);
}

etag-for computes an ETag from a value, using its cache-key or to-hash when available. ETags are weak by default (W/"..."); pass weak => False for a strong ETag. Matching uses a weak comparison, so W/ prefixes are ignored, and * matches anything.

Cache headers

expires-in sets Cache-Control with a max-age, private by default or public, plus any extra directives. expires-now marks the response uncacheable:

self.expires-in(3600, public => True, must-revalidate => True);
# Cache-Control: public, max-age=3600, must-revalidate

self.expires-now;                  # Cache-Control: no-cache
self.expires-now(no-store => True); # Cache-Control: no-store

Cache store

MVC::Keayl::Cache provides a Store role with a low-level key-value API shared by every backend:

$store.write('user/1', $user, expires-in => 300);
$store.read('user/1');
$store.exist('user/1');
$store.delete('user/1');

$store.fetch('user/1', { load-user(1) }, expires-in => 300);

fetch returns a cached value or computes, stores, and returns it. force recomputes, skip-nil declines to store an undefined result, and race-condition-ttl serves the stale value to other readers while one recomputes an expired entry.

increment and decrement maintain counters, preserving an entry's expiry window across updates. read-multi and write-multi work on several keys at once, and delete-matched removes every key matching a string glob or a regex:

$store.increment('visits', 1, expires-in => 3600);
$store.write-multi({ a => 1, b => 2 });
$store.read-multi('a', 'b');
$store.delete-matched('user/*');

Entries carry an optional version; a read with a mismatched version misses. A store can take a namespace that prefixes its keys and a default-expires-in applied when a write gives no expiry.

Backends

  • MemoryStore keeps entries in process, with an optional max-entries bound that evicts the least recently used entry.

  • FileStore persists each entry under a root directory, surviving across instances.

  • NullStore stores nothing, so fetch always recomputes. It is the no-op backend for test and development.

  • ExternalStore delegates to an injected client shaped like a Redis or Memcache driver (get, set with a ttl, del, keys), serializing each entry through it.

Fragment caching

A store's fetch backs view fragment caching. cache-key derives a key from parts (an object contributes its own cache-key), under a views/ namespace, with an optional template digest:

cache-key($post, digest => template-digest($source));   # views/posts/1-2021/<digest>

A view caches a fragment with cache-fragment, which computes the content once per key through its configured store:

$view.cache-fragment([$post], { render-the-fragment() });

Rate limiting

rate-limit is a controller class method that caps how often a client may reach an action, backed by a cache store. It registers a before-action that counts requests per discriminator within a window and blocks once the count passes the limit:

PostsController.rate-limit(to => 100, within => 60);

It also has an is rate-limit trait form for the class header:

class PostsController is MVC::Keayl::Controller is rate-limit(to => 100, within => 60) { }

By default the discriminator is the request's remote IP and an over-limit request gets 429 Too Many Requests with a Retry-After header set to the window. by supplies a custom discriminator and with a custom over-limit handler; store and name choose the backing store and the counter name:

ApiController.rate-limit(
  to => 1000, within => 3600,
  by    => -> $controller { $controller.api-key },
  with  => -> $controller { $controller.head(503) },
  store => $cache,
);

Streaming

A response body can be streamed instead of buffered. stream takes an iterable of chunks (strings or blobs); stream-chunks yields them encoded, and is-streaming reports whether a stream is set, for an adapter to write chunked:

$response.stream(gather { for @rows { take row-html($_) } });

This is the pull-based primitive: the whole sequence is known up front. For a push-based response, where an action writes chunks over time, see live streaming.

Range requests

send-file honours a Range request header, responding 206 Partial Content with a Content-Range header and the requested byte slice, and advertises Accept-Ranges: bytes.

MVC::Keayl v0.9.1

Model-View-Controller web framework

Authors

  • Greg Donald

License

Artistic-2.0

Dependencies

Cro::HTTPCrypt::RandomDigest::HMACDigest::SHA1::NativeOpenSSLMIME::Base64YAMLishORM::ActiveRecord:auth<zef:gdonald>Template::HAML:auth<zef:gdonald>

Provides

  • MVC::Keayl
  • MVC::Keayl::APIController
  • MVC::Keayl::ActionText
  • MVC::Keayl::ActionText::Repository
  • MVC::Keayl::ActionText::RichTextable
  • MVC::Keayl::Adapter
  • MVC::Keayl::Adapter::Cro
  • MVC::Keayl::Adapter::Test
  • MVC::Keayl::Application
  • MVC::Keayl::Assets
  • MVC::Keayl::Assets::Serving
  • MVC::Keayl::CLI
  • MVC::Keayl::CSRF
  • MVC::Keayl::Cable::Broadcasting
  • MVC::Keayl::Cable::Channel
  • MVC::Keayl::Cable::Connection
  • MVC::Keayl::Cable::PubSub
  • MVC::Keayl::Cable::PubSub::External
  • MVC::Keayl::Cable::PubSub::InMemory
  • MVC::Keayl::Cache
  • MVC::Keayl::Caching
  • MVC::Keayl::Config
  • MVC::Keayl::Controller
  • MVC::Keayl::Cookies
  • MVC::Keayl::Credentials
  • MVC::Keayl::Dispatcher
  • MVC::Keayl::Endpoint
  • MVC::Keayl::Engine
  • MVC::Keayl::ErrorReporter
  • MVC::Keayl::ErrorReporting
  • MVC::Keayl::Errors
  • MVC::Keayl::ExceptionPage
  • MVC::Keayl::Flash
  • MVC::Keayl::HealthController
  • MVC::Keayl::Helpers::Asset
  • MVC::Keayl::Helpers::DateTime
  • MVC::Keayl::Helpers::Form
  • MVC::Keayl::Helpers::Number
  • MVC::Keayl::Helpers::Options
  • MVC::Keayl::Helpers::Tag
  • MVC::Keayl::Helpers::Text
  • MVC::Keayl::Helpers::Url
  • MVC::Keayl::HttpAuthentication
  • MVC::Keayl::I18n
  • MVC::Keayl::I18n::Locale
  • MVC::Keayl::I18n::Pluralization
  • MVC::Keayl::Job
  • MVC::Keayl::Job::GlobalID
  • MVC::Keayl::Job::QueueAdapter
  • MVC::Keayl::Job::QueueAdapter::Async
  • MVC::Keayl::Job::QueueAdapter::Database
  • MVC::Keayl::Job::QueueAdapter::Inline
  • MVC::Keayl::Job::QueueAdapter::Test
  • MVC::Keayl::Live
  • MVC::Keayl::LogEvent
  • MVC::Keayl::Logger
  • MVC::Keayl::Mail
  • MVC::Keayl::Mailbox
  • MVC::Keayl::Mailbox::Ingress
  • MVC::Keayl::Mailbox::Router
  • MVC::Keayl::Mailer
  • MVC::Keayl::Mailer::Delivery
  • MVC::Keayl::Mailer::Delivery::File
  • MVC::Keayl::Mailer::Delivery::SMTP
  • MVC::Keayl::Mailer::Delivery::Test
  • MVC::Keayl::Mailer::DeliveryJob
  • MVC::Keayl::Mailer::Preview
  • MVC::Keayl::Middleware
  • MVC::Keayl::Middleware::Database
  • MVC::Keayl::Middleware::HostAuthorization
  • MVC::Keayl::Middleware::Logger
  • MVC::Keayl::Middleware::RequestId
  • MVC::Keayl::Middleware::SSL
  • MVC::Keayl::Middleware::SecureHeaders
  • MVC::Keayl::Middleware::Static
  • MVC::Keayl::MiddlewareStack
  • MVC::Keayl::Mime
  • MVC::Keayl::Notifications
  • MVC::Keayl::PWAController
  • MVC::Keayl::ParameterFilter
  • MVC::Keayl::Parameters
  • MVC::Keayl::Params
  • MVC::Keayl::Request
  • MVC::Keayl::Response
  • MVC::Keayl::Router
  • MVC::Keayl::Routing
  • MVC::Keayl::Routing::Mount
  • MVC::Keayl::Routing::PathPattern
  • MVC::Keayl::Routing::Redirect
  • MVC::Keayl::Routing::Resources
  • MVC::Keayl::Routing::Route
  • MVC::Keayl::Routing::RouteMatch
  • MVC::Keayl::Routing::UrlHelpers
  • MVC::Keayl::SafeString
  • MVC::Keayl::Secrets
  • MVC::Keayl::Session
  • MVC::Keayl::Storage
  • MVC::Keayl::Storage::Attached
  • MVC::Keayl::Storage::Repository
  • MVC::Keayl::Storage::Service
  • MVC::Keayl::Storage::Serving
  • MVC::Keayl::Storage::Variant
  • MVC::Keayl::TestSupport
  • MVC::Keayl::View
  • MVC::Keayl::View::Context
  • MVC::Keayl::View::Handler
  • MVC::Keayl::View::Handler::HAML

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.