=pod
=head1 NAME
Kelp::Manual - Reference to web development
with
Kelp
=head1 SYNOPSIS
First ...
sub
build {
my
$self
=
shift
;
my
$r
=
$self
->routes;
$r
->add(
"/hello"
,
sub
{
"Hello, world!"
} );
$r
->add(
'/hello/:name'
,
'greet'
);
}
sub
greet {
my
(
$self
,
$name
) =
@_
;
"Hello, $name!"
;
}
1;
Then ...
my
$app
= MyApp->new;
$app
->run;
Finally ...
> plackup app.psgi
Or,
for
quick prototyping
use
L<Kelp::Less>:
get
'/hello/?name'
=>
sub
{
my
(
$self
,
$name
) =
@_
;
"Hello "
.
$name
//
'world'
;
};
run;
=head1 DESCRIPTION
If you're going to be deploying a Perl based web application, chances are that
you will be using L<Plack>. Plack
has
almost all necessary tools to create and
maintain a healthy web app. Tons of middleware is written
for
it, and there are
several very well tested high performance preforking servers, such as L<Gazelle>.
Plack, however, is not a web framework, hence its creators have intentionally
omitted adding certain components. This is where Kelp gets to shine. It provides
a layer on top of Plack and puts everything together into a complete web
framework.
=head1 CREATING A NEW WEB APP
=head2 Quick development using Kelp::Less
For writing quick experimental web apps and to reduce the boiler plate, one
could
use
L<Kelp::Less>. In this case all of the code can be put in C<app.psgi>:
Look up the POD
for
L<Kelp::Less>
for
many examples, but to get you started off,
here is a quick one:
module
'JSON'
;
get
'/api/:user/?action'
=>
sub
{
my
(
$self
,
$user
,
$action
) =
@_
;
my
$json
= {
success
=> \1,
user
=>
$user
,
action
=>
$action
//
'ask'
};
return
$json
;
};
run;
=head2 Using the C<kelp-generator> script
The easiest way to create the directory structure and a general application
skeleton is by using the C<kelp-generator> script, which comes
with
this
package
.
> kelp-generator MyApp
This will create C<lib/MyApp.pm>, C<app.psgi> and some other files (explained
below).
To create a L<Kelp::Less> app file,
use
:
> kelp-generator --type=less MyApp
Get help by typing:
> kelp-generator --help
=head2 Directory structure
Before you begin writing the internals of your app, you need to create the
directory structure either by hand, or by using the above described C<kelp-generator>
utility script.
.
|--/lib
| |--MyApp.pm
| |--/MyApp
|
|--/conf
| |--config.pl
| |--test.pl
| |--development.pl
| |--deployment.pl
|
|--/views
|--/
log
|--/t
|--app.psgi
=over
=item B</lib>
The C<lib> folder contains your application modules and any
local
modules
that you want your app to
use
.
=item B</conf>
The C<conf> folder is where Kelp will look
for
configuration files. You need one
main file, named C<config.pl>. You can also add other files that define different
running environments,
if
you name them I<environment>C<.pl>. Replace
I<environment>
with
the actual name of the environment.
To change the running environment, you can specify the app C<mode>, or you can
set the C<PLACK_ENV> environment variable.
my
$app
= MyApp->new(
mode
=>
'development'
);
or
> PLACK_ENV=development plackup app.psgi
=item B</views>
This is where the C<Template> module will look
for
template files.
=item B</
log
>
This is where the C<Logger> module will create C<error.
log
> and
any other
log
files that were
defined
in the configuration.
=item B</t>
The C<t> folder is traditionally used to hold test files. It is up to you to
use
it or not, although we strongly recommend that you
write
some automated test
units
for
your web app.
=item B<app.psgi>
This is the L<PSGI> file, of the app, which you will deploy. In it's most basic
form it should look like this:
my
$app
= MyApp->new;
$app
->run;
=back
=head2 The application classes
Your application's classes should be put in the C<lib/> folder. The main class,
in
our
example C<MyApp.pm>, initializes any modules and variables that your
app will
use
. Here is an example that uses C<Moose> to create lazy attributes
and initialize a database connection:
has
dbh
=> (
is
=>
'ro'
,
isa
=>
'DBI'
,
lazy
=> 1,
default
=>
sub
{
my
$self
=
shift
;
my
@config
= @{
$self
->config(
'dbi'
) };
return
DBI->
connect
(
@config
);
}
);
sub
build {
my
$self
=
shift
;
$self
->routes->add(
"/read/:id"
,
"read"
);
}
sub
read
{
my
(
$self
,
$id
) =
@_
;
$self
->dbh->selectrow_array(
q[
SELECT * FROM problems
WHERE id = ?
]
,
$id
);
}
1;
What is happening here?
=over
=item
First, we create a lazy attribute and instruct it to
connect
to DBI. Notice that
we have access to the current app and all of its internals via the C<
$self
>
variable. Notice also that the reason we define C<dbh> as a I<lazy> attribute
is that C<config> will not yet be initialized. All modules are initialized upon
the creation of the object instance, e.g.
when
we call C<MyApp-E<gt>new>;
=item
Then, we
override
Kelp's L<Kelp/build> subroutine to create a single route
C</
read
/:id>, which is assigned to the subroutine C<
read
> in the current class.
=item
The C<
read
> subroutine, takes C<
$self
> and C<
$id
> (the named placeholder from the
path), and uses C<
$self
-E<gt>dbh> to retrieve data.
=back
I<A note about object managers:> The above example uses L<Moose>. It is entirely
up to you to
use
Moose, another object manager, or
no
object manager at all.
The above example will be just as successful
if
you used
our
own little
L<Kelp::Base>:
attr
dbi
=>
sub
{
...
};
1;
=head1 FRAMEWORK BASICS
=head2 Routing
Kelp uses a powerful and very flexible router. Traditionally, it is also light
and consists of less than 400 lines of code (comments included). You are
encouraged to
read
L<Kelp::Routes>, but here are some key points. All examples
are assumed to be inside the L<Kelp/build> method and C<
$r
> is equal to
C<
$self
-E<gt>routes>:
=head3 Destinations
You can direct HTTP paths to subroutines in your classes or, you can
use
inline
code.
$r
->add(
"/home"
,
"home"
);
$r
->add(
"/legal"
,
"Legal::view"
);
$r
->add(
"/about"
,
sub
{
"Content for about"
});
=head3 Restrict HTTP methods
Make a route only
catch
a specific HTTP method:
$r
->add( [
POST
=>
'/update'
],
"update_user"
);
=head3 Nesting Plack apps
It's easy to have a Plack app nested in Kelp:
$r
->add(
'/app'
, {
to
=>
$plack_app
->to_app,
psgi
=> 1,
});
See L<Kelp::Routes/PLACK APPS>
for
details.
=head3 Named captures
Using regular expressions is so Perl. Sometimes, however, it gets a little
overwhelming. Use named paths
if
you anticipate that you or someone
else
will
ever want to maintain your code.
=head4 Explicit
$r
->add(
"/update/:id"
,
"update"
);
sub
update {
my
(
$self
,
$id
) =
@_
;
}
=head4 Optional
$r
->add(
"/person/?name"
,
sub
{
my
(
$self
,
$name
) =
@_
;
return
"I am "
.
$name
//
"nobody"
;
});
This will handle C</person>, C</person/> and C</person/jack>.
=head4 Wildcards
$r
->add(
'/*article/:id'
,
'Articles::view'
);
This will handle C</bar/foo/baz/500> and
send
it to C<MyApp::Articles::view>
with
parameters C<
$article
> equal to C<bar/foo/baz> and C<
$id
> equal to 500.
Wildcards can also be used without a label:
$r
->add(
'/actions*'
=>
sub
{ ... } );
NOTE: matched contents from an unlabelled wildcard will be B<discarded>
if
your
route also contains named placeholders. Name it to prevent that from happening.
=head4 Slurpy
$r
->add(
'/other-app/>rest'
=>
sub
{
my
(
$self
,
$rest
) =
@_
;
return
"other-app called with path: "
. (
$rest
//
'<none>'
);
} );
This is a mix of L</Wildcards> and L</Optional>. It works like optional
placeholders but will by
default
also match slashes.
The
use
case of this is to have something that hijacks all possibilities under
that path, but also matches
for
that base path,
for
example the above will
match all of these:
/other-app/>rest matches:
/other-app
/other-app/
/other-app/home
/other-app/article/1
/other-app/
*rest
matches:
/other-app/home
/other-app/article/1
/other-app/?rest matches:
/other-app
/other-app/
/other-app/home
Just like wildcards, slurpy placeholders can be used without a label:
$r
->add(
'/user/actions/>'
=>
sub
{ ... } );
NOTE: matched contents from an unlabelled slurpy will be B<discarded>
if
your
route also contains named placeholders. Name it to prevent that from happening.
=head3 Placeholder restrictions
Paths' named placeholders can be restricted by providing regular expressions.
$r
->add(
'/user/:id'
, {
check
=> {
id
=>
'\d+'
},
to
=>
"Users::get"
});
=head3 Placeholder defaults
This only applies to optional placeholders, or those prefixed
with
a question mark.
If a
default
value is provided
for
any of them, it will be used in case the
placeholder value is missing.
$r
->add(
'/:id/?other'
,
defaults
=> {
other
=>
'info'
} );
=head3 Bridges
A I<bridge> is a route that
has
to
return
a true value in order
for
the
next
route in line to be processed.
$r
->add(
'/users'
, {
to
=>
'Users::auth'
,
bridge
=> 1 } );
$r
->add(
'/users/:action'
=>
'Users::dispatch'
);
See L<Kelp::Routes/BRIDGES>
for
more information.
=head3 Route order
Routes will be executed in order that will usually be the one you want. Bridges
will execute
before
normal routes, and the routes will be sorted by patterns
using Perl C<cmp>. However, you can sometimes run into trouble
with
their ordering.
In that case, you can
use
special key C<order> to
sort
it out. All routes have
default
C<order> of C<0>. If you want some of them to execute earlier, reduce
their order value. Late routes can be marked
with
positive order.
Of course, even
if
you specify order, bridges will still always come
before
regular routes.
=head3 URL building
Each path can be
given
a name and later a URL can be built using that name and
the necessary arguments.
$r
->add(
"/update/:id"
, {
name
=>
'update'
,
to
=>
'User::update'
} );
my
$url
=
$self
->route->url(
'update'
,
id
=> 1000);
=head2 Reading a HTTP request
All input data comes nicely packed inside L<Kelp::Request>, which inherits
Plack::Request. It
has
a coulpe convenience methods and handles charset
decoding automatically.
=head3 Input data charsets
All request methods showcased below will
try
to decode request data
with
either
charset from the C<Content-Type> header (
if
present and supported by L<Encode>
module) or
with
application charset otherwise.
There are a couple methods starting
with
C<raw_> which
return
encoded data. See
L<Kelp::Request/ENCODING>
for
details.
=head3 C<param> and friends
The request class
has
a couple of C<param> methods, which allow quick and easy access to request parameters.
sub
fetch_params {
my
$self
=
shift
;
my
$key
=
'parameter_name'
;
my
$json_or_body_or_query
=
$self
->param(
$key
);
my
$always_query
=
$self
->res->query_param(
$key
);
my
$always_body
=
$self
->res->body_param(
$key
);
my
$always_json
=
$self
->res->json_param(
$key
);
}
These C<param> methods
return
a single value
with
a C<
$key
> or a list of
available
keys
with
no
arguments.
=head3 C<parameters> and friends
These methods
return
a L<Hash::MultiValue> object
with
parameters:
sub
fetch_parameters {
my
$self
=
shift
;
my
$body_or_query
=
$self
->res->parameters(
$key
);
my
$always_query
=
$self
->res->query_parameters(
$key
);
my
$always_body
=
$self
->res->body_parameters(
$key
);
}
They may be more useful to get a lot of parameters in one go.
=head3 C<content>, C<raw_body> and C<json_content>
These methods
return
the body of the request.
C<content> returns the body properly decoded.
C<json_content> tries to decode the C<content> as json and
return
a Perl
structure or C<
undef
> on error or
if
it isn't a json request.
C<raw_body> is same as C<content>, but it
has
the original request encoding.
=head3 File uploads
The request object
has
a C<uploads|Plack::Request/uploads> property. The
uploads property returns a reference to a hash containing all uploads.
sub
upload {
my
$self
=
shift
;
my
$uploads
=
$self
->req->uploads;
...
}
For L<Kelp::Less>, then you can
use
the C<req> reserved word:
get
'/upload'
=>
sub
{
my
$uploads
= req->uploads;
};
=head3 Other request data
See L<Kelp::Request> and L<Plack::Request> to see how to fetch some other data
you may find useful.
=head2 Building an HTTP response
Kelp contains an elegant module, called L<Kelp::Response>, which
extends
C<Plack::Response>
with
several useful methods. Most methods
return
C<
$self
>
after
they
do
the required job. For the sake of the examples below, let's
assume that all of the code is located inside a route definition.
=head3 Automatic content type
Your routes don't always have to set the C<response> object. You could just
return
a simple
scalar
value or a reference to a hash, array or anything that
can be converted to JSON.
sub
text_route {
return
"There, there ..."
;
}
sub
json_route {
return
{
error
=> 1,
message
=>
"Fail"
};
}
=head3 Automatic charset encoding
With Kelp, you don't have to worry about the encoding of the response - most of
the methods will automatically encode the response into configured
application's charset. Text and application content types will by
default
have
C<charset> part added. To make it all work flawlessly, remember to C<
use
utf8;>
at the top of your files.
If you'd like to instead take charset into your own hands, you can configure
L<Kelp/charset> and L<Kelp/request_charset> to undefined
values
. Alternatively,
you can
use
C<raw_> methods in L<Kelp::Request> and
L<Kelp::Response/render_binary> and manually set content types and charsets.
=head3 Rendering text
$self
->res->text->render(
"It works!"
);
=head3 Rendering HTML
$self
->res->html->render(
"<h1>It works!</h1>"
);
=head3 Custom content type
$self
->res->set_content_type(
'image/png'
);
=head3 Return 404 or 500 errors
sub
some_route {
my
$self
=
shift
;
if
(
$missing
) {
return
$self
->res->render_404;
}
if
(
$broken
) {
return
$self
->res->render_500;
}
}
=head3 Templates
sub
hello {
my
(
$self
,
$name
) =
@_
;
$self
->res->template(
'hello.tt'
, {
name
=>
$name
} );
}
The above example will render the contents of C<hello.tt>, and it will set the
content-type to C<text/html>. To set a different content-type,
use
C<set_content_type> or any of its aliases:
sub
hello_txt {
my
(
$self
,
$name
) =
@_
;
$self
->res->text->template(
'hello_txt.tt'
, {
name
=>
$name
} );
}
You can also simply get template string and
return
it, which will work the same:
sub
hello {
my
(
$self
,
$name
) =
@_
;
return
$self
->template(
'hello.tt'
, {
name
=>
$name
} );
}
=head3 Rendering DATA
Kelp templates can easily render from C<DATA> or other filehandle:
sub
hello {
my
(
$self
,
$name
) =
@_
;
return
$self
->template( \
*DATA
, {
name
=>
$name
} );
}