NAME

Kelp::Manual::Cookbook - Recipes for Kelp dishes

DESCRIPTION

This document lists solutions to common problems you may encounter while developing your own Kelp web application. Since Kelp leaves a lot for you to figure out yourself (also known as not getting in your way) many of these will be just a proposed solutions, not an official way of solving a problem.

RECIPES

Setting up a common layout for all templates

Kelp does not implement template layouts by itself, so it's up to templating engine or contributed module to deliver that behavior. For example, Template::Toolkit allows for WRAPPER directive, which can be used like this (with Kelp::Module::Template::Toolkit):

# in config
modules => [qw(Template::Toolkit)],
modules_init => {
    'Template::Toolkit' => {
        WRAPPER => 'layouts/main.tt',
    },
},

Connecting to DBI

There are multiple ways to do it, like the one below:

# Private attribute holding DBI handle
# anonymous sub is a default value builder
attr _dbh => sub {
    shift->_dbi_connect;
};

# Private sub to connect to DBI
sub _dbi_connect {
    my $self = shift;

    my @config = @{ $self->config('dbi') };
    return DBI->connect(@config);
}

# Public method to use when you need dbh
sub dbh {
    my $self = shift;

    # ping is likely not required, but just in case...
    if (!$self->_dbh->ping) {
        # reload the dbh, since ping failed
        $self->_dbh($self->_dbi_connect);
    }

    $self->_dbh;
}

# Use $self->dbh from here on ...

sub some_route {
    my $self = shift;

    $self->dbh->selectrow_array(q[
        SELECT * FROM users
        WHERE clue > 0
    ]);
}

A slightly shorter version with state variables and no ping:

# Public method to use when you need dbh
sub dbh {
    my ($self, $reconnect) = @_;

    state $handle;
    if (!defined $handle || $reconnect) {
        my @config = @{ $self->config('dbi') };
        $handle = DBI->connect(@config);
    }

    return $handle;
}

# Use $self->dbh from here on ...

sub some_route {
    my $self = shift;

    $self->dbh->selectrow_array(q[
        SELECT * FROM users
        WHERE clue > 0
    ]);
}

Same methods can be used for accessing the schema of <DBIx::Class>.

Custom 404 and 500 error pages

Error templates

The easiest way to set up custom error pages is to create templates in views/error/ with the code of the error. For example: views/error/404.tt and views/error/500.tt. You can render those manually using $self->res->render_404 and $self->res->render_500. To render another error code, you can use $self->res->render_error.

Within the route

You can set the response headers and content within the route:

sub some_route {
    my $self = shift;
    $self->res->set_code(404)->template('my_404_template');
}

By overriding the Kelp::Response class

To make custom 500, 404 and other error pages, you will have to subclass the Kelp::Response module and override the render_404 and render_500 subroutines. Let's say your app's name is Foo and its class is in lib/Foo.pm. Now create a file lib/Foo/Response.pm:

package Foo::Response;
use Kelp::Base 'Kelp::Response';

sub render_404 {
    my $self = shift;
    $self->template('my_custom_404');
}

sub render_500 {
    my $self = shift;
    $self->template('my_custom_500');
}

Then, in lib/Foo.pm, you have to tell Kelp to use your custom response class like this:

sub response {
    my $self = shift;
    return Foo::Response->new( app => $self );
}

Don't forget you need to create views/my_custom_404.tt and views/my_custom_500.tt. You can add other error rendering subroutines too, for example:

sub render_401 {
    # Render your custom 401 error here
}

Altering the behavior of a Kelp class method

The easiest solution would be to use KelpX::Hooks module available on CPAN:

use KelpX::Hooks;
use parent "Kelp";

# Change how template rendering function is called
hook "template" => sub {
    my ($orig, $self, @args) = @_;

    # $args[0] is template name
    # $args[1] is a list of template variables
    $args[1] = {
        (defined $args[1] ? %{$args[1]} : ()),
        "my_var" => $self->do_something,
    };

    # call the original $self->template again
    # with modified arguments
    return $self->$orig(@args);
};

Handling websocket connections

Since Kelp is a Plack-based project, its support for websockets is very limited. First of all, you would need a Plack server with support for the psgi streaming, io and nonblocking, like Twiggy. Then, you could integrate Kelp application with a websocket application via Kelp::Module::Websocket::AnyEvent CPAN module (if the server implementation is compatible with AnyEvent):

sub build {
    my ($self) = @_;

    my $ws = $self->websocket;
    $ws->add(message => sub {
        my ($conn, $msg) = @_;

        $conn->send({echo => $msg});
    });

    $self->symbiosis->mount("/ws" => $ws);
}

Keep in mind that Plack websockets are a burden because of lack of preforking server implementations capable of running them. If you want to use them heavily you're better off using Mojolicious instead or integrating a Mojo::Server::Hypnotoad with a small Mojo application alongside Kelp as a websocket handler.

Deploying

Deploying a Kelp application is done the same way any other Plack application is deployed:

> plackup -E deployment -s Gazelle app.psgi

In production environments, it is usually a good idea to set up a proxy between the PSGI server and the World Wide Web. Popular choices are apache2 and nginx. To get full information about incoming requests, you'll also need to use Plack::Middleware::ReverseProxy.

# app.psgi

builder {
    enable_if { ! $_[0]->{REMOTE_ADDR} || $_[0]->{REMOTE_ADDR} =~ /127\.0\.0\.1/ }
    "Plack::Middleware::ReverseProxy";
    $app->run;
};

(REMOTE_ADDR is not set at all when using the proxy via filesocket).

SEE ALSO

Kelp::Manual

Kelp

Plack

SUPPORT