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
SUPPORT
GitHub: https://github.com/sgnix/kelp
Mailing list: https://groups.google.com/forum/?fromgroups#!forum/perl-kelp