middleware

Middleware

MVC::Keayl processes a request through a stack of middleware wrapped around a single innermost endpoint. The model mirrors Rack: each middleware receives the downstream app, can act before and after delegating to it, and may short-circuit by returning its own response.

The endpoint protocol

An endpoint is anything that does the MVC::Keayl::Endpoint role: a single call method that takes a Request and returns a Response:

use MVC::Keayl::Endpoint;
use MVC::Keayl::Request;
use MVC::Keayl::Response;

class Hello does MVC::Keayl::Endpoint {
  method call(MVC::Keayl::Request:D $request --> MVC::Keayl::Response:D) {
    MVC::Keayl::Response.new(:body('Hello'));
  }
}

The router (see Routing) is the endpoint that sits innermost in the stack.

Writing middleware

A middleware subclasses MVC::Keayl::Middleware. The base class holds the downstream app in app and, by default, passes the request straight through. Override call to wrap that delegation:

use MVC::Keayl::Middleware;

class Timing is MVC::Keayl::Middleware {
  method call(MVC::Keayl::Request:D $request --> MVC::Keayl::Response:D) {
    # ... before ...
    my $response = self.app.call($request);
    # ... after ...
    $response;
  }
}

A middleware that returns a response without calling self.app short-circuits the rest of the stack, useful for authentication gates, caching, and the like.

The stack

MVC::Keayl::MiddlewareStack is an ordered, named collection of middleware. The first entry added is the outermost (it sees the request first and the response last).

use MVC::Keayl::MiddlewareStack;

my $stack = MVC::Keayl::MiddlewareStack.new;

$stack.use('logger', Logging);
$stack.use('timing', Timing);

Construction arguments after the class are forwarded to the middleware's new alongside app:

$stack.use('host-guard', HostAuthorization, :allowed<example.com>);

Reordering

Entries are addressed by name:

$stack.insert-before('timing', 'request-id', RequestId);
$stack.insert-after('logger', 'ssl', ForceSSL);
$stack.delete('timing');

insert-before and insert-after raise if the named target is not in the stack.

Introspection

$stack.names;            # ('logger', 'timing'), in order
$stack.elems;            # 2
$stack.contains('ssl');  # False

Building the app

build wraps an endpoint with the whole stack and returns the resulting app, itself an endpoint you call:

my $app = $stack.build($router);

my $response = $app.call($request);

An empty stack returns the endpoint unwrapped.

Serving static files

MVC::Keayl::Middleware::Static serves files from a directory and passes every other request through to the app. It suits development and small deployments; in production a front-end server such as Apache or nginx usually serves the files directly.

use MVC::Keayl::Middleware::Static;

$stack.prepend('static', MVC::Keayl::Middleware::Static, root => 'public'.IO);

A request maps to a file beneath root, so GET /css/app.css serves public/css/app.css. Only GET and HEAD are handled; any other method, a missing file, or a directory passes through to the app. The content type is derived from the file extension, falling back to application/octet-stream. Parent-directory segments are refused, so a request cannot escape root.

url-prefix serves a directory beneath a path prefix, stripping it before the lookup. With the settings below, GET /assets/css/app.css serves assets/css/app.css, while a path outside the prefix passes through:

$stack.prepend('static', MVC::Keayl::Middleware::Static,
  root       => 'assets'.IO,
  url-prefix => '/assets');

Prepending keeps it outermost, so a matched file short-circuits before routing.

Per-request database connections

MVC::Keayl::Middleware::Database gives each request its own pooled connection per database it touches. When a database is configured, the application prepends it automatically through the active-record-connection initializer, so you do not wire it by hand.

It binds a request-scoped connection registry for the duration of the request. The first model query on a connection checks one out of that connection's pool and caches it; every later query on the same connection reuses it, so the whole request sees a consistent connection and transaction. The connections return to their pools when the request ends, including when the request raises.

use MVC::Keayl::Middleware::Database;

$stack.prepend('db-connection', MVC::Keayl::Middleware::Database);

The registry is keyed by connection name, so an app with a read replica or several databases routes each model to its own pooled connection rather than forcing everything onto one. The pool verifies a connection on checkout and reconnects a dropped one, so a socket that died between requests heals on the next request instead of failing every query until the server restarts.

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.