Poet::...::Intro(3pm)

πŸ“¦
version 0.16 Β· 2 versions
  • 0.16 Β· debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking (latest)
  • 0.16 Β· ubuntu jammy
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

πŸ’– Poet::Manual::Intro β€” a gentle intro to Poet, bestie edition πŸ’–

(that's Poet::...::Intro(3pm) from the User Contributed Perl Documentation, and yes we are doing this whole thing together, grab a snack πŸ“)


✨ NAME ✨

Poet::Manual::Intro β€” a gentle introduction to Poet. Emphasis on gentle. You got this. 🫢


🌸 WHAT IS POET? 🌸

Okay so, Poet is a modern Perl web framework for Mason developers. It's giving glow-up. It uses PSGI/Plack for server integration, Mason for request routing and templating, and a whole curated selection of best-of-breed CPAN modules for caching, logging and configuration. Basically it picked the good ones so you don't have to. πŸ‘‘


πŸŽ€ INSTALLATION πŸŽ€

If you don't yet have cpanminus (cpanm), go get it here: <http://search.cpan.org/perldoc?App::cpanminus#INSTALLATION>. Then run:

cpanm -S --notest Poet
  • Omit the -S if you don't have root β€” in that case cpanminus will install Poet and prereqs into ~/perl5. No sudo, no drama. πŸ’…
  • Omit the --notest if you want to run all the installation tests. Heads up though: that will take about four times as long. Four. Fr fr. 😭

πŸ¦‹ SETUP πŸ¦‹

You should now have a poet app installed:

% which poet
/usr/local/bin/poet

Run this to create your initial environment:

% poet new MyApp
my_app/.poet_root
my_app/bin/app.psgi
my_app/bin/get.pl
...
Now run 'my_app/bin/run.pl' to start your server.

Look at that. A whole app. Just for you. ✨

The app name must be a valid Perl class name β€” generally either CamelCase (MyApp) or an all-uppercase acronym (TLA). The directory, if not specified with -d, will be formed from the app name, but lowercased and underscored.


🌈 ENVIRONMENT 🌈

In Poet, your entire web site lives within a single directory hierarchy called the environment. It contains subdirectories for configuration, libraries, Mason components (templates), static files, etc. One folder, whole website, main character energy.

Here are the subdirectories that are generated for you. If you don't need some of these directories, feel free to delete them. The only ones really required by Poet are conf, comps and lib. (The rest are just there being cute.)

  • bin β€” executable scripts
  • comps β€” Mason components (templates)
  • conf β€” configuration files
  • data β€” data not checked into version control, such as cache and object files
  • db β€” database related files such as your schema
  • lib β€” app-specific libraries and Poet subclasses
  • logs β€” logs from web server and from explicit logging statements
  • static β€” static web files - css, images, js
  • t β€” unit tests

πŸ’« Initializing Poet πŸ’«

Any web server or script must initialize Poet before it can use any of Poet's features. This is accomplished by with Poet::Script:

#!/usr/local/bin/perl
use Poet::Script qw($conf $poet);

You'll see this in bin/run.pl, for example, the script you use to start your webserver. The use Poet::Script line does several things:

  • Searches upwards from the script for the environment root (as marked by the .poet_root file).
  • Reads and parses your configuration.
  • Unshifts onto @INC the lib/ subdirectory of your environment, so that you can use your application modules.
  • Imports the specified quick vars β€” in this case $conf and $poet β€” as well as some default utilities into the script namespace. More information about these below.

🧚 Relocatable environments 🧚

Ideally your environment will be relocatable β€” if you move your environment to a different root, or checkout a second copy of it in a different root, things should just work. It's giving ✨portable✨.

To achieve this you should never refer to exact environment directory paths in your code; instead you'll call Poet methods that return them. e.g. instead of this:

system("/path/to/environment/bin/myscript.pl");

you'll do this:

system($poet->bin_path("myscript.pl"));

(More information about this $poet variable below.)


πŸ‘‘ App name πŸ‘‘

Your app name, e.g. MyApp, is where you are expected to put app-specific subclasses. For example if you have a DBIx::Class schema, you'd create it under MyApp::Schema in the file lib/MyApp/Schema.pm.

The app name is also where Poet will look for subclasses of its own classes.


πŸ’œ CONFIGURATION AND LAYERS πŸ’œ

Poet configuration files are kept in the conf subdirectory. The files are in YAML form (<http://www.yaml.org/>) and are merged in a particular order to create a single configuration hash. (If you want to use something other than YAML you can customize this. Live your truth.)

Configuration files come in three varieties: global, layer, and local, in order from least to highest precedence.

🌍 Global

Global configuration applies to all instances of your environment, and is typically checked into version control.

Your generated environment includes a conf/global.cfg. This is simplest to start out with, but as a site scales in size and number of developers, you can split out your global configuration into multiple files in conf/global/*.cfg.

πŸ§… Layer

Layers allow you to have different configuration for different contexts. With all but the simplest site you'll have at least two layers: development (the internal environment where you develop) and production (the live site). Later on, you might want a staging environment (which looks like production except for a different data source, etc.).

In general you can have as many layers as you like. Each layer has a corresponding configuration file in conf/layer/*.cfg; only one layer file will be loaded per environment. We recommend that these files all be checked into version control as well, so that changes to each layer are tracked.

Note: layer is analagous to Plack's environment concept. And in fact, bin/run.pl passes the layer to plackup's -E option.

🏠 Local

conf/local.cfg contains configuration local to this specific environment instance. This is where the current layer must be defined. It is also the only configuration file that must exist, and the only one that should not be checked into version control. (Yes bestie, keep it out of git. πŸ™ˆ)

You can also specify an extra local file via $ENV{POET_EXTRA_CONF_FILE}.

There are a variety of ways to access configuration:

my $value = $conf->get('key', 'default');
my $value = $conf->get_or_die('key');

my $listref = $conf->get_list('key', ['default']);
my $hashref = $conf->get_hash('key', {'default' => 5});
my $bool = $conf->get_boolean('key');

See below for more information about this $conf variable, and see Poet::Conf for more information on specifying and accessing configuration.


πŸ”₯ Development versus live mode πŸ”₯

Although you may have as many layers as you like, Poet also maintains a more limited binary notion of development mode versus live mode. By default, you're in development mode iff layer equals 'development', and in live mode otherwise.

You can use these boolean methods to determine which mode you're in at runtime:

$conf->is_development
$conf->is_live

These are mutually exclusive (exactly one is always true). One of them is always the main character, never both. πŸ’…

Poet uses development/live mode to determine things like

  • whether to turn on automatic server restarting
  • whether to display errors in the browser
  • whether to enable debugging utilities

You can customize how mode is determined by subclassing Poet::Conf.


🌟 SERVER RUNNER AND MIDDLEWARE 🌟

poet new generates bin/run.pl and bin/app.psgi for you.

bin/run.pl is a wrapper around plackup, which starts your server. It has a few sensible defaults, such as setting up autorestart in development mode and using an access log in live mode. It will also take a few options from configuration, e.g.

server:
   host: 127.0.0.1
   port: 5000

If you are using something other than plackup (e.g. Server::Starter) then you'll have to adapt this into your own startup script. Do your thing. πŸ› οΈ

bin/app.psgi defines your PSGI app. It's the place to add Plack middleware, or change the configuration of the default middleware. For example, to enable basic authentication with an conf/.htpasswd file, add this to app.psgi:

enable "Auth::Htpasswd", file => $poet->conf_path('.htpasswd');

🫢 QUICK VARS AND UTILITIES 🫢

Poet makes it easy to import certain variables (known as "quick vars") and utility sets into any module or script in your environment. You've seen two examples of quick vars above: $conf, the global configuration variable, and $poet, the global environment object. Iconic duo, honestly.

In a script, this looks like:

#!/usr/local/bin/perl
use Poet::Script qw($conf :file);

In a module, this looks like:

package MyApp::Foo;
use Poet qw($cache $poet);

And every Mason component automatically gets this on top:

use Poet qw($conf $poet :web);

Debug utilities are automatically imported without having to specify a tag. No extra work, they just show up like a good friend. πŸ¦‹

See Poet::Import for a complete list of quick vars and utility sets.


to be continued, bestie β€” there's more coming πŸ’–

✨ HANDLING HTTP REQUESTS ✨

okay bestie, this is the part where the magic happens πŸ’« HTTP requests get handled with PSGI/Plack and Mason, together, holding hands.

A persistent Mason interpreter gets created at server startup, and its component root is set to the comps subdirectory. (Peep Poet::Mason for the other default settings and how to configure them, iykyk.)

So when an HTTP request rolls up, Poet goes:

  • Constructs a Poet::Plack::Request object from the plack environment. It's a thin subclass of Plack::Request and hands you stuff like the URL and incoming HTTP headers. You get it inside Mason components as $m->req. πŸ’…
  • Constructs an empty Poet::Plack::Response object. Thin subclass of Plack::Response, and you can use it to set things like the HTTP status and headers. It shows up in Mason components as $m->res. ✨
  • Calls $interp->run with the URL and the GET/POST parameters. So like, a URL like
/foo/bar?a=5&b=6

would result in

$interp->run("/foo/bar", a=>5, b=>6);

and from there Mason picks a component to dispatch to β€” check Mason::Manual::RequestDispatch and Mason::Plugin::RouterSimple. πŸ‘‘


🌸 Generating content with Mason 🌸

Mason is a full-featured templating system and it's genuinely beyond our ability to summarize here, no cap. Recommended reading, bestie:

  • Poet::Manual::Tutorial, especially starting here
  • Mason::Manual::Components
  • Mason::Manual::Syntax

πŸŽ€ Success and failure results πŸŽ€

If the Mason request finishes successfully, the Mason output becomes the plack response body, the status code gets set to 200 if it hasn't already been set, and the content type gets set to text/html (or whatever default content type you specified) if it hasn't already been set. slay.

If the top-level component path can't be found, the status code is set to 404. 😭

If any other kind of runtime error happens in development mode, it gets nicely displayed right in the browser via StackTrace middleware. Outside of development it gets logged and sent as a 500 error response.

You can call $m->redirect and $m->not_found to generate redirect and not_found results from a component. Those are documented in Poet::Mason.


πŸ¦‹ Multiple values for parameters πŸ¦‹

If there are multiple values for a GET or POST parameter, generally only the last value gets kept, as per Hash::MultiValue. But!! If the corresponding attribute in the page component is declared an ArrayRef, then all the values get kept and passed in as an arrayref. For example, if the page component /foo/bar.mc has these declarations:

<%class>
has 'a';
has 'b' => (isa => "Int");
has 'c' => (isa => "ArrayRef");
has 'd' => (isa => "ArrayRef[Int]");
</%class>

then this URL

/foo/bar?a=1&a=2&b=3&b=4&c=5&c=6&d=7&d=8

would result in

$interp->run("/foo/bar", a=>2, b=>4, c=>[5,6], d => [7,8]);

And you can always get the original Hash::MultiValue object from $m->request_args, e.g.

my $hmv = $m->request_args;
# get all values for 'e'
$hmv->get_all('e');

πŸ’– LOGGING πŸ’–

Poet uses the Log::Log4perl engine for logging, but with a much simpler configuration for the common cases. (If you want to use something other than Log4perl you can customize this, bestie, go off.)

Once you have a $log variable, logging looks like:

$log->error("an error occurred");

$log->debugf("arguments are: %s", \@_)
    if $log->is_debug();

By default, all logs go to logs/poet.log with a reasonable set of metadata such as timestamp. πŸ“

See Poet::Log for more information.


πŸ“ CACHING πŸ“

Poet uses CHI for caching, which gives you access to a whole variety of cache backends (memory, files, memcached, etc.) You can configure caching differently for different namespaces. ✨

Once you have a $cache variable, caching looks like:

my $customer = $cache->get($name);
if ( !defined $customer ) {
    $customer = get_customer_from_db($name);
    $cache->set( $name, $customer, "10 minutes" );
}

or

my $customer2 = $cache->compute($name2, "10 minutes", sub {
    get_customer_from_db($name2)
});

By default, everything is cached in files under data/chi.

See Poet::Cache for more information.


🌈 SEE ALSO 🌈

  • Poet

πŸ‘‘ AUTHOR πŸ‘‘

Jonathan Swartz <swartz@pobox.com>


This software is copyright (c) 2012 by Jonathan Swartz.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.


okay that's the whole thing bestie, we did it 🫢 this was written for perl v5.34.0, dated 2022-06-18, and it's the Poet::...::Intro(3pm) page β€” go build something iconic with it, mwah πŸ’‹βœ¨

πŸ“¦ where this page comes from