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).
Changing the default access logging
Access logs reported by Kelp through logger
can be modified or disabled by writing your own customized "before_dispatch" in Kelp method (not calling the parent version).
sub before_dispatch {} # enough to disable the access logs
Using sessions
In order to have access to "session" in Kelp::Request a Plack::Middleware::Session middleware must be initialized. In your config file:
middleware => ['Session'],
middleware_init => {
Session => {
store => 'File'
}
}
Note that you pretty much need to choose a store
right away, as otherwise it will store data in memory, which is both volatile and does not work with multi-process servers.
SEE ALSO
SUPPORT
GitHub: https://github.com/sgnix/kelp
Mailing list: https://groups.google.com/forum/?fromgroups#!forum/perl-kelp