diff --git a/Changes b/Changes index 8f74b8b..4a4d7ee 100644 --- a/Changes +++ b/Changes @@ -11,7 +11,6 @@ - Module::Template now inserts an 'app' parameter to render variables unless 'app' is already provided [Changes] - - Scalar context behavior of the param method called without arguments on json request is now deprecated * this is done in effort to make param method easier to use and harder to misuse - Kelp::Test no longer uses HTTP::Cookies, implements a much slimmer cookie jar with the same interface * The new cookie jar only stores key/value pairs without any special data for cookies like domains, paths or expiration dates @@ -30,6 +29,8 @@ - A template named '0' will now be properly rendered - An 'app' object available in Kelp::Response template is now a properly reblessed controller + [Deprecations] + - Scalar context behavior of the param method called without arguments on json request is now deprecated [Backward-Incompatible Changes] - Request data will now be properly decoded using either charset from Content-Type or application charset diff --git a/lib/Kelp/Manual.pod b/lib/Kelp/Manual.pod index ad3efbe..eada063 100644 --- a/lib/Kelp/Manual.pod +++ b/lib/Kelp/Manual.pod @@ -62,8 +62,46 @@ 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. +=head2 New Kelp versions and backward compatibility + +Major version 2 of Kelp introduced some breaking changes, especially when it +comes to how requests are decoded and how the errors are rendered. If you are +affected and don't want to modify your code, you are welcome to use a fixed +version C<1.07> (before the big changes). You will be missing on a lot of +improvements though. + +Kelp values backward compatibility, but at the same time it will not be +hesitant to fix bugs, security issues or major inconveniences where it sees +necessary. From 2.01 forward, non-bugfix breaking changes will only be +introduced after a 3-month deprecation period. Changelog will list them under +C<[Deprecations]> section. + =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. In this case all of the code can be put in C: +Look up the POD for L for many examples, but to get you started off, +here is a quick one: + + # app.psgi + use Kelp::Less; + + 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 script The easiest way to create the directory structure and a general application @@ -74,7 +112,7 @@ skeleton is by using the C script, which comes with this package. This will create C, C and some other files (explained below). -To create a L app, use: +To create a L app file, use: > Kelp --type=less MyApp @@ -230,6 +268,8 @@ L: 1; +=head1 FRAMEWORK BASICS + =head2 Routing Kelp uses a powerful and very flexible router. Traditionally, it is also light @@ -387,106 +427,204 @@ the necessary arguments. my $url = $self->route->url('update', id => 1000); # /update/1000 -=head2 Reblessing into controller classes +=head2 Reading a HTTP request -All of the examples here show routes which take a instance of the web -application as a first parameter. This is true even if those routes live in -another class. +All input data comes nicely packed inside L, which inherits +Plack::Request. It has a coulpe convenience methods and handles charset +decoding automatically. -To rebless the app instance into the controller class instance, the controller -class must extend the class specified in C configuration field. The -default value of that field is the application class, so your application class -is by default your main controller class. All other controllers must (directly -or indirectly) inherit from your application class. +=head3 Input data charsets -=head3 Step 1: Configure the controller +All request methods showcased below will try to decode request data with either +charset from the C header (if present and supported by L +module) or with application charset otherwise. -It is a good practice to set up a different C, so that you separate general -app code from request-handling code. +There are a couple methods starting with C which return encoded data. See +L for details. - # config.pl - { - modules_init => { - Routes => { - rebless => 1, # the app instance will be reblessed - base => 'MyApp::Controller', - } - } +=head3 C and friends + +The request class has a couple of C methods, which allow quick and easy access to request parameters. + + sub fetch_params { + my $self = shift; + my $key = 'parameter_name'; + + # fetch parameters from query form, body form or JSON body + 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); } -=head3 Step 2: Create a main controller class +These C methods return a single value with a C<$key> or a list of +available keys with no arguments. -This step is only required if you've changed the C. +=head3 C and friends - # lib/MyApp/Controller.pm - package MyApp::Controller; - use Kelp::Base 'MyApp'; +These methods return a L object with parameters: - # Now $self is an instance of 'MyApp::Controller'; - sub service_method { + sub fetch_parameters { my $self = shift; - ...; + + # fetch parameters from query form or body form + my $body_or_query = $self->res->parameters($key); + my $always_query = $self->res->query_parameters($key); + my $always_body = $self->res->body_parameters($key); } - 1; +They may be more useful to get a lot of parameters in one go. -=head3 Step 3: Create any number of controller classes +=head3 C, C and C -They all must inherit from your main controller class. +These methods return the body of the request. - # lib/MyApp/Controller/Users.pm - package MyApp::Controller::Users; - use Kelp::Base 'MyApp::Controller'; +C returns the body properly decoded. - # Now $self is an instance of 'MyApp::Controller::Users' - sub authenticate { +C tries to decode the C as json and return a Perl +structure or C on error or if it isn't a json request. + +C is same as C, but it has the original request encoding. + +=head3 File uploads + +The request object has a C property. The +uploads property returns a reference to a hash containing all uploads. + + sub upload { my $self = shift; - ...; + my $uploads = $self->req->uploads; + + # Now $uploads is a hashref to all uploads + ... } - 1; +For L, then you can use the C reserved word: -=head3 Step 4: Add routes with shorter class names + get '/upload' => sub { + my $uploads = req->uploads; + }; -You no longer have to prefix destinations with the base controller class name. +=head3 Other request data - # lib/MyApp.pm +See L and L to see how to fetch some other data +you may find useful. - ... +=head2 Building an HTTP response - sub build { +Kelp contains an elegant module, called L, which extends +C 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 object. You could just +return a simple scalar value or a reference to a hash, array or anything that +can be converted to JSON. + + # Content-type automatically set to "text/html" + sub text_route { + return "There, there ..."; + } + + # Content-type automatically set to "application/json" + 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. Plaintext and HTML content types will by default have +C part added. To make it all work flawlessly, remember to C +at the top of your files. + +If you'd like to instead take charset into your own hands, you will have to use +L and manually set content types and charsets. + +=head3 Rendering text + + # Render simple text + $self->res->text->render("It works!"); + +=head3 Rendering HTML + + $self->res->html->render("

It works!

"); + +=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; + } + } - # if 'base' was not changed, this would have to be written as: - # => 'Controller::Users::authenticate' - $self->add_route('/login' => 'Users::authenticate'); +=head3 Templates + sub hello { + my ( $self, $name ) = @_; + $self->res->template( 'hello.tt', { name => $name } ); } +The above example will render the contents of C, and it will set the +content-type to C. To set a different content-type, use +C or any of its aliases: -=head2 Quick development using Kelp::Less + sub hello_txt { + my ( $self, $name ) = @_; + $self->res->text->template( 'hello_txt.tt', { name => $name } ); + } -For writing quick experimental web apps and to reduce the boiler plate, one -could use L. In this case all of the code can be put in C: -Look up the POD for C for many examples, but to get you started off, -here is a quick one: +=head3 Headers - # app.psgi - use Kelp::Less; + $self->set_header( "X-Framework", "Kelp" )->render( { success => \1 } ); - module 'JSON'; +=head3 Serving static files - get '/api/:user/?action' => sub { - my ( $self, $user, $action ) = @_; - my $json = { - success => \1, - user => $user, - action => $action // 'ask' - }; - return $json; +If you want to serve static pages, you can use the L +middleware that comes with Plack. Here is an example configuration that serves +files in your C folder (under the Kelp root folder) from URLs that +begin with C: + + # conf/config.pl + { + middleware => [qw/Static/], + middleware_init => { + Static => { + path => qr{^/public/}, + root => '.', + } + } }; - run; +=head3 Delayed responses + +To send a delayed response, have your route return a subroutine. + + sub delayed { + my $self = shift; + return sub { + my $responder = shift; + $self->res->code(200); + $self->res->text->body("Better late than never."); + $responder->($self->res->finalize); + }; + } + +See the L pod for more +information and examples. + +=head1 CONFIGURING THE APPLICATION =head2 Adding middleware @@ -542,221 +680,181 @@ returned app. Note that any middleware defined in your config file will be added first. -=head2 Testing +=head2 Pluggable modules -Kelp provides a test class called C. It is object oriented, and all -methods return the C object, so they can be chained together. -Testing is done by sending HTTP requests to an already built application and -analyzing the response. Therefore, each test usually begins with the -L method, which takes a single L parameter. -It sends the request to the web app and saves the response as an -L object. +=head3 How to load modules using the config - # file t/test.t - use MyApp; - use Kelp::Test; - use Test::More; - use HTTP::Request::Common; +Kelp can be extended using custom I. There are two modules that are +B loaded by each application instance. Those are C and +C. The reason behind this is that each and every application always +needs a router and configuration. All other modules must be loaded either using +the L method, or using the C key in the +configuration. The default configuration already loads these modules: +C