Initial Commit

This commit is contained in:
Riley Schneider
2025-12-03 16:38:10 +01:00
parent c5e26bf594
commit b732d8d4b5
17680 changed files with 5977495 additions and 2 deletions

View File

@@ -0,0 +1,264 @@
=encoding utf8
=head1 NAME
Mojolicious::Guides::Contributing - Contributing to Mojolicious
=head1 OVERVIEW
There are many ways to contribute to L<Mojolicious>, this guide will show you a few of them.
=head1 REPORTING BUGS
We use the L<GitHub issue tracker|https://github.com/mojolicious/mojo/issues>, so you'll need to create a (free)
GitHub account to be able to submit issues, comments and pull requests.
First of all, make sure you are using the latest version of L<Mojolicious>, it is quite likely that your bug has
already been fixed. If that doesn't help, take a look at the list of currently open issues, perhaps it has already been
reported by someone else and you can just add a comment confirming it.
If it hasn't been reported yet, try to prepare a test case demonstrating the bug, you are not expected to fix it
yourself, but you'll have to make sure the developers can replicate your problem. Sending in your whole application
generally does more harm than good, the C<t> directory of this distribution has many good examples for how to do it
right. Writing a test is usually the hardest part of fixing a bug, so the better your test case the faster it can be
fixed. ;)
And don't forget to add a descriptive title and text, when you create a new issue. If your issue does not contain
enough information or is unintelligible, it might get closed pretty quickly. But don't be disheartened, if there's new
activity it will get reopened just as quickly.
=head2 Reporting security issues
Please report security issues directly to Sebastian Riedel (C<kraih@mojolicious.org>), and give us a few days to
develop and release a proper fix.
=head1 RESOLVING ISSUES
There are many ways in which you can help us resolve existing issues on the L<GitHub issue
tracker|https://github.com/mojolicious/mojo/issues>.
Can you replicate the problem on your computer? Add a comment saying that you're seeing the same. Perhaps you can
provide additional information that will make it easier for others to replicate the problem, maybe even contribute a
better test case.
And for all code contributions we very much appreciate additional testing and code review, just add a comment to show
your approval or to point out flaws that need to be addressed.
=head1 CONTRIBUTING DOCUMENTATION
One of the easiest ways to contribute to L<Mojolicious> is through documentation improvements. While the
L<Mojolicious::Guides> are carefully curated by the core team, everybody with a (free) GitHub account can make changes
and add new information to the L<Mojolicious wiki|https://github.com/mojolicious/mojo/wiki>.
Pull requests with additions or changes to the documentation included in the L<Mojolicious> distribution follow the
same rules as code contributions. Please don't send pull requests for overly simplistic changes, such as the addition
of a comma or semicolon.
=head1 CONTRIBUTING CODE
All code contributions should be sent as L<GitHub pull requests|https://help.github.com/articles/using-pull-requests>.
But please try to avoid pull requests with very simplistic changes, such as a single typo fix somewhere in the
documentation or comments.
An expressive title and detailed description are invaluable during the review process, which usually ends when members
of the community have voiced their opinions and the core team reviewed the changes. For a pull request to get merged it
requires three positive reviews from voting members of the core team.
All code changes should emulate the style of the surrounding code, include tests that fail without them, and update
relevant documentation.
While the L<Mojolicious> distribution covers a wide range of features, we are rather conservative when it comes to
adding new ones. So if your contribution is not a simple bug fix, it is B<strongly recommended> that you discuss it in
advance in the L<Forum|https://forum.mojolicious.org> or the official IRC channel C<#mojo> on C<chat.freenode.net>
(L<chat now!|https://webchat.freenode.net/#mojo>), to avoid unnecessary work and to increase its chances of getting
accepted.
The following mission statement and rules are the foundation of all L<Mojo> and L<Mojolicious> development. Please make
sure that your contribution aligns well with them before sending a pull request.
=head2 Mission statement
L<Mojo> is a web development toolkit, with all the basic tools and helpers needed to write simple web applications and
higher level web frameworks, such as L<Mojolicious>.
All components should be reusable in other projects, and in a UNIXish way only loosely coupled.
Especially for people new to Perl it should be as easy as possible to install L<Mojolicious> and get started. Writing
web applications can be one of the most fun ways to learn a language!
For developers of other web frameworks, it should be possible to reuse all the infrastructure and just consider the
higher levels of the L<Mojolicious> distribution an example application.
=head2 Rules
General rules for the project:
=over 2
Web development should be easy and fun, this is what we optimize for.
The web is a moving target, to stay relevant we have to stay in motion too.
Keep it simple, no magic unless absolutely necessary.
The installation process should be as fast and painless as possible. (Less than a minute on most common hardware is a
good rule of thumb)
It's not a feature without a test and documentation.
A feature is only needed when the majority of the user base benefits from it.
Features may only be changed in a major release, to fix a serious security issue, or after being deprecated for at
least 3 months.
Refactoring and deprecations should be avoided if there are no substantial benefits.
New features can be marked as experimental to be excluded from deprecation policies.
A major release is signaled by a new major version number and a unique code name based on a Unicode character.
Only add dependencies if absolutely necessary and make them optional if possible.
Emulate the style of the existing code and documentation, but don't be afraid to adopt newer best practices if you can
apply them consistently.
Domain specific languages should be avoided in favor of Perl-ish solutions.
Documentation belongs to the guides, module POD is just an API reference.
The main focus of the included documentation should be on examples, no walls of text. (An example for every one or two
sentences is a good rule of thumb)
Everything should be ordered alphabetically if possible, or at least be consistent if not.
The master source code repository should always be kept in a stable state, use feature branches for actual development.
Code has to be run through L<Perl::Tidy> with the included
L<.perltidyrc|https://github.com/mojolicious/mojo/blob/master/.perltidyrc>, and everything should look like it was
written by a single person.
Functions and methods should be as short as possible, no spaghetti code.
Comments should be correctly capitalized, and funny if possible, punctuation is optional if it doesn't increase
readability.
No names outside of C<Mojolicious.pm>.
=back
=head2 Voting Rules
The voting process used to make decisions for the project:
=over 2
A feature can be added or modified when at least 3 members of the core team have cast a vote in favour, or the BDFL
overruled the vote.
Any core team member may nominate new members, who must then be accepted by a 2/3 majority vote.
Sebastian has veto rights on all decisions and will resolve issues that could not be decided with a vote.
=back
=head1 CODE OF CONDUCT
Like the technical community as a whole, the L<Mojolicious> team and community is made up of a mixture of professionals
and volunteers from all over the world, working on every aspect of the mission - including mentorship, teaching, and
connecting people.
Diversity is one of our huge strengths, but it can also lead to communication issues and unhappiness. To that end, we
have a few ground rules that we ask people to adhere to. This code applies equally to founders, mentors and those
seeking help and guidance.
This isn't an exhaustive list of things that you can't do. Rather, take it in the spirit in which its intended - a
guide to make it easier to enrich all of us and the technical communities in which we participate.
This code of conduct applies to all spaces managed by the L<Mojolicious> project. This includes IRC, the mailing lists,
the issue tracker, and any other forums created by the project team which the community uses for communication. In
addition, violations of this code outside these spaces may affect a person's ability to participate within them.
If you believe someone is violating the code of conduct, we ask that you report it by emailing Joel Berger
(C<jberger@mojolicious.org>) or other members of L<the team|Mojolicious/"Core Developers">.
=over 2
=item * B<Be friendly and patient.>
=item * B<Be welcoming.> We strive to be a community that welcomes and supports people of all backgrounds and
identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour,
immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and
expression, age, size, family status, political belief, religion, and mental and physical ability.
=item * B<Be considerate.> Your work will be used by other people, and you in turn will depend on the work of others.
Any decision you take will affect users and colleagues, and you should take those consequences into account when making
decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary
language.
=item * B<Be respectful.> Not all of us will agree all the time, but disagreement is no excuse for poor behavior and
poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a
personal attack. Its important to remember that a community where people feel uncomfortable or threatened is not a
productive one. Members of the L<Mojolicious> community should be respectful when dealing with other members as well as
with people outside the L<Mojolicious> community.
=item * B<Be careful in the words that you choose.> We are a community of professionals, and we conduct ourselves
professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary
behavior aren't acceptable. This includes, but is not limited to:
=over 2
=item * Violent threats or language directed against another person.
=item * Discriminatory jokes and language.
=item * Posting sexually explicit or violent material.
=item * Posting (or threatening to post) other people's personally identifying
information ("doxing").
=item * Personal insults, especially those using racist or sexist terms.
=item * Unwelcome sexual attention.
=item * Advocating for, or encouraging, any of the above behavior.
=item * Repeated harassment of others. In general, if someone asks you to stop,
then stop.
=back
=item * B<When we disagree, try to understand why.> Disagreements, both social and technical, happen all the time and
L<Mojolicious> is no exception. It is important that we resolve disagreements and differing views constructively.
Remember that were different. The strength of L<Mojolicious> comes from its varied community, people from a wide range
of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a
viewpoint doesnt mean that theyre wrong. Dont forget that it is human to err and blaming each other doesnt get us
anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
=back
=head1 FORK POLICY
The L<Mojolicious> core team believes that there is a lot of value in the entire toolkit being a unified project. Forks
drain resources from a project, not just mindshare but also very valuable bug reports and patches, which can have very
serious security implications. Therefore we ask that you please not publically fork pieces of the L<Mojolicious>
distribution without our consent. As doing so is against our express wishes, individuals who engage in unauthorized
forking may be denied from participating in community sponsored spaces.
For developers considering the use of a forked module, we strongly recommend that you make yourself familiar with its
history and track record. While many parts of L<Mojolicious> have been forked in the past, very few forks have been
able to keep up with L<Mojolicious> development, and most are missing critical bug fixes.
=head1 MORE
You can continue with L<Mojolicious::Guides> now or take a look at the L<Mojolicious
wiki|https://github.com/mojolicious/mojo/wiki>, which contains a lot more documentation and examples by many different
authors.
=head1 SUPPORT
If you have any questions the documentation might not yet answer, don't hesitate to ask in the
L<Forum|https://forum.mojolicious.org> or the official IRC channel C<#mojo> on C<chat.freenode.net>
(L<chat now!|https://webchat.freenode.net/#mojo>).
=cut

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,250 @@
=encoding utf8
=head1 NAME
Mojolicious::Guides::FAQ - Frequently Asked Questions
=head1 OVERVIEW
This document contains answers for the most frequently asked questions about L<Mojolicious>.
=head1 QUESTIONS
We hope these answers are to your satisfaction.
=head2 How does Mojolicious compare to other Perl web frameworks?
The short answer is "it doesn't", because we interpret the term "web framework" much more literally than others. With
the emergence of the real-time web and new technologies such as WebSockets, we are facing new challenges that go way
beyond what commonly used modules like L<LWP> were designed for. Because of this, L<Mojolicious> contains a whole new
HTTP client/server stack called L<Mojo>, which was heavily inspired by the original LWPng effort and carefully designed
with these new requirements in mind. So while some of the higher abstraction layers might look similar to other web
frameworks, it is more of a web toolkit and can even be used as the foundation for more advanced web frameworks.
=head2 Why doesn't Mojolicious have any dependencies?
We are optimizing L<Mojolicious> for user-friendliness and development speed, without compromises. While there are no
rules in L<Mojolicious::Guides::Contributing> that forbid dependencies, we do currently discourage adding non-optional
ones in favor of a faster and more painless installation process. And we do in fact already use several optional CPAN
modules such as L<Cpanel::JSON::XS>, L<EV>, L<IO::Socket::Socks>, L<IO::Socket::SSL>, L<Net::DNS::Native>, L<Plack> and
L<Role::Tiny> to provide advanced functionality if possible.
=head2 Why reinvent wheels?
Because we can make them rounder. Components specifically designed for user-friendliness and development speed are not
easy to come by. We are strong believers of the Perl mantra "There is more than one way to do it", and our quest is to
develop the best possible solutions for these two criteria.
=head2 What about backwards compatibility?
In conformance with L<Mojolicious::Guides::Contributing>, we will always deprecate a feature for 3 months, before
removing or changing it in incompatible ways between major releases. New features can however be marked as experimental
to explicitly exclude them from these rules. This gives us the necessary freedom to ensure a healthy future for
L<Mojolicious>. So, as long as you are not using anything marked experimental, untested or undocumented, you can always
count on backwards compatibility, everything else would be considered a bug. However, to completely avoid any risk of
accidental breakage, we do recommend following current best practices for version pinning with L<Carton> for production
setups.
=head2 Why not split up Mojolicious into many smaller distributions?
Because there are no advantages, it drastically increases maintenance costs and installation times without giving us
anything in return. It would only make sense if we wanted to pass ownership of a module to a new maintainer, which we
already have done in the past.
=head2 Where can i discuss my patches for Mojolicious?
We'd love to discuss your contributions to L<Mojolicious> on our official IRC channel C<#mojo> on C<chat.freenode.net>
(L<chat now!|https://webchat.freenode.net/#mojo>).
=head2 Which versions of Perl are supported by Mojolicious?
First of all, you need to be aware that according to the L<perlpolicy>, only the two most recent stable release series
of Perl are supported by the community and receive bug fixes, which are currently 5.30.x and 5.28.x. L<Mojolicious>
follows this model and fully supports these two release series. In addition we will also keep the distribution
installable (and that means passing all tests) up to a certain legacy version that the core team deems worthy of
supporting, but not specifically optimize for it, this is currently 5.16.0.
=head2 How well is Windows supported by Mojolicious?
Windows is not officially supported by L<Mojolicious>, even though we try to keep the distribution installable. There
may be serious security and/or reliability issues. Some of the more advanced features, such as
L<subprocesses|Mojo::IOLoop/"subprocess"> and the L<Hypnotoad|Mojo::Server::Hypnotoad> web server, will also require
the use of the L<Windows Subsystem for Linux|https://msdn.microsoft.com/commandline/wsl/>.
=head2 Is Perl's taint mode supported by Mojolicious?
No. There is no benefit at all to using taint mode. Modern Perl applications are much too complex to benefit from such a
naive mechanism in any meaningful way. At best it would give you a false sense of security.
=head2 Do I need to clean my environment before testing Mojolicious?
L<Mojolicious> uses many environment variables both internally and externally, notably (but not exclusively) those
starting with the prefix C<MOJO_*> and C<PLACK_ENV>. The test suite expects a clean environment; testing with a
non-standard environment is unsupported and is unlikely to succeed. Therefore when installing or upgrading
L<Mojolicious> and when running its tests, we highly recommend using an environment which does not set these variables.
=head2 Where did my file extension go?
Standard route placeholders will not match the C<.> character, however L<Mojolicious> routes automatically take file
extensions like C<.html>, remove the leading C<.>, and store the result in the C<format> stash value. This can be
useful for URL-based content negotiation, such as automatically rendering different templates based on the file
extension. See L<Mojolicious::Guides::Routing/"Formats"> for information on customizing format detection, or consider
using L<relaxed placeholders|Mojolicious::Guides::Routing/"Relaxed placeholders"> to allow matching of the C<.>
character.
=head2 Can I configure Hypnotoad from the command line?
No, you can't, L<Hypnotoad|Mojo::Server::Hypnotoad> is a bit special in this regard. Because when you initiate a zero
downtime software upgrade (hot deployment), you are only really sending a C<USR2> signal to the already running server,
and no other information can be passed along. What you can do instead, is to use a L<Mojolicious::Plugin::Config>,
L<Mojolicious::Plugin::JSONConfig> or L<Mojolicious::Plugin::NotYAMLConfig> configuration file.
# myapp.conf
{
hypnotoad => {
listen => ['http://*:8080'],
workers => 10
}
};
Or if you don't actually need zero downtime software upgrades, just use L<Mojolicious::Command::prefork> instead, which
is otherwise almost identical to Hypnotoad.
$ ./myapp.pl prefork -m production -l http://*:8080 -w 10
=head2 What does the error "...certificate verify failed" mean?
There are many variations of this error, but most of them mean that TLS certificate verification in L<Mojo::UserAgent>
failed. This usually happens for two reasons. The most common one is that the peer certificate is simply invalid. If
that's the case and you are certain that no MITM attack is being attempted, you can use the attribute
L<Mojo::UserAgent/"insecure"> or C<MOJO_INSECURE> environment variable to disable certificate verification. And if
that's not the case you might be missing the L<Mozilla::CA> module, which is often required by L<IO::Socket::SSL> to be
able to verify certificates.
=head2 What does the error "Maximum message size exceeded" mean?
To protect your applications from excessively large requests and responses, our HTTP parser has a cap after which it
will automatically stop accepting new data, and in most cases force the connection to be closed. The limit is 16MiB for
requests, and 2GiB for responses by default. You can use the attributes L<Mojolicious/"max_request_size"> and
L<Mojo::UserAgent/"max_response_size"> to change these values.
=head2 What does the error "Maximum start-line size exceeded" mean?
This is a very similar protection mechanism to the one described in the previous answer, but a little more specific. It
limits the maximum length of the start-line for HTTP requests and responses. The limit is 8KiB by default, you can use
the attribute L<Mojo::Message/"max_line_size"> or C<MOJO_MAX_LINE_SIZE> environment variable to change this value.
=head2 What does the error "Maximum header size exceeded" mean?
Almost the same as the previous answer, but this protection mechanism limits the number and maximum length of HTTP
request and response headers. The limits are 100 headers with 8KiB each by default, you can use the attributes
L<Mojo::Headers/"max_lines"> and L<Mojo::Headers/"max_line_size"> or the C<MOJO_MAX_LINES> and C<MOJO_MAX_LINE_SIZE>
environment variables to change these values.
=head2 What does the error "Maximum buffer size exceeded" mean?
This protection mechanism limits how much content the HTTP parser is allowed to buffer when parsing chunked, compressed
and multipart messages. The limit is around 256KiB by default, you can use the attribute
L<Mojo::Content/"max_buffer_size"> or C<MOJO_MAX_BUFFER_SIZE> environment variable to change this value.
=head2 What does "Your secret passphrase needs to be changed" mean?
L<Mojolicious> uses secret passphrases for security features such as signed cookies. It defaults to using
L<Mojolicious/"moniker">, which is not very secure, so we added this log message as a reminder. You can change the
passphrase with the attribute L<Mojolicious/"secrets">. Since some plugins also depend on it, you should try changing
it as early as possible in your application.
$app->secrets(['My very secret passphrase.']);
=head2 What does "Nothing has been rendered, expecting delayed response" mean?
L<Mojolicious> has been designed from the ground up for non-blocking I/O and event loops. So when a new request comes
in and no response is generated right away, it will assume that this was intentional and return control to the web
server, which can then handle other requests while waiting for events such as timers to finally generate a response.
=head2 What does "Inactivity timeout" mean?
To protect your applications from denial-of-service attacks, all connections have an inactivity timeout which limits
how long a connection may be inactive before being closed automatically. It defaults to C<40> seconds for the user
agent and C<30> seconds for all built-in web servers, and can be changed with the attributes
L<Mojo::UserAgent/"inactivity_timeout"> and L<Mojo::Server::Daemon/"inactivity_timeout"> or the
C<MOJO_INACTIVITY_TIMEOUT> environment variable. In L<Mojolicious> applications you can also use the helper
L<Mojolicious::Plugin::DefaultHelpers/"inactivity_timeout"> to change it on demand for each connection individually.
This timeout always applies, so you might have to tweak it for applications that take a long time to process a request.
=head2 What does "Premature connection close" mean?
This error message is often related to the one above, and means that the web server closed the connection before the
user agent could receive the whole response or that the user agent got destroyed, which forces all connections to be
closed immediately.
# The variable $ua goes out of scope and gets destroyed too early
Mojo::IOLoop->timer(5 => sub {
my $ua = Mojo::UserAgent->new;
$ua->get('https://mojolicious.org' => sub ($ua, $tx) {
say $tx->result->dom->at('title')->text;
});
});
=head2 What does "Worker 31842 has no heartbeat (50 seconds), restarting" mean?
As long as they are accepting new connections, worker processes of all built-in pre-forking web servers send heartbeat
messages to the manager process at regular intervals, to signal that they are still responsive. A blocking operation
such as an infinite loop in your application can prevent this, and will force the affected worker to be restarted after
a timeout. This timeout defaults to C<50> seconds and can be extended with the attribute
L<Mojo::Server::Prefork/"heartbeat_timeout"> if your application requires it.
=head2 What does "Transaction already destroyed" mean?
This error message usually appears after waiting for the results of a non-blocking operation for longer periods of
time, because the underlying connection has been closed in the meantime and the value of the attribute
L<Mojolicious::Controller/"tx"> is no longer available. While there might not be a way to prevent the connection from
getting closed, you can try to avoid this error message by keeping a reference to the transaction object that is not
weakened.
# Keep a strong reference to the transaction object
my $tx = $c->render_later->tx;
$c->ua->get_p('https://mojolicious.org')->then(sub {
$c->render(text => 'Visited mojolicious.org');
})->catch(sub ($err) {
$tx;
$c->reply->exception($err);
});
=head2 What does "Illegal character in prototype" mean?
Mojolicious assumes L<subroutine signatures|Mojolicious::Guides/"Signatures"> are enabled in documentation examples. If
the signatures feature has not been enabled in that scope, they are interpreted as L<prototypes|perlsub/"Prototypes">,
an unrelated parser feature. Mojolicious does not require signatures; if you don't want to or cannot use signatures
(which require Perl 5.20+), you can translate most signatures into a standard subroutine parameter assignment.
# With signatures feature
get '/title' => sub ($c) {
$c->ua->get('mojolicious.org' => sub ($ua, $tx) {
$c->render(data => $tx->result->dom->at('title')->text);
});
};
# Without signatures feature
get '/title' => sub {
my ($c) = @_;
$c->ua->get('mojolicious.org' => sub {
my ($ua, $tx) = @_;
$c->render(data => $tx->result->dom->at('title')->text);
});
};
=head1 MORE
You can continue with L<Mojolicious::Guides> now or take a look at the L<Mojolicious
wiki|https://github.com/mojolicious/mojo/wiki>, which contains a lot more documentation and examples by many different
authors.
=head1 SUPPORT
If you have any questions the documentation might not yet answer, don't hesitate to ask in the
L<Forum|https://forum.mojolicious.org> or the official IRC channel C<#mojo> on C<chat.freenode.net>
(L<chat now!|https://webchat.freenode.net/#mojo>).
=cut

View File

@@ -0,0 +1,771 @@
=encoding utf8
=head1 NAME
Mojolicious::Guides::Growing - Growing Mojolicious applications
=head1 OVERVIEW
This document explains the process of starting a L<Mojolicious::Lite> prototype from scratch and growing it into a
well-structured L<Mojolicious> application.
=head1 CONCEPTS
Essentials every L<Mojolicious> developer should know.
=head2 Model View Controller
MVC is a software architectural pattern for graphical user interface programming originating in Smalltalk-80, that
separates application logic, presentation and input.
+------------+ +-------+ +------+
Input -> | Controller | -> | Model | -> | View | -> Output
+------------+ +-------+ +------+
A slightly modified version of the pattern moving some application logic into the I<controller> is the foundation of
pretty much every web framework these days, including L<Mojolicious>.
+----------------+ +-------+
Request -> | | <-> | Model |
| | +-------+
| Controller |
| | +-------+
Response <- | | <-> | View |
+----------------+ +-------+
The I<controller> receives a request from a user, passes incoming data to the I<model> and retrieves data from it,
which then gets turned into an actual response by the I<view>. But note that this pattern is just a guideline that most
of the time results in cleaner more maintainable code, not a rule that should be followed at all costs.
=head2 REpresentational State Transfer
REST is a software architectural style for distributed hypermedia systems such as the web. While it can be applied to
many protocols it is most commonly used with HTTP these days. In REST terms, when you are opening a URL like
C<http://mojolicious.org/foo> with your browser, you are basically asking the web server for the HTML I<representation>
of the C<http://mojolicious.org/foo> I<resource>.
+--------+ +--------+
| | -> http://mojolicious.org/foo -> | |
| Client | | Server |
| | <- <html>Mojo rocks!</html> <- | |
+--------+ +--------+
The fundamental idea here is that all resources are uniquely addressable with URLs and every resource can have
different representations such as HTML, RSS or JSON. User interface concerns are separated from data storage concerns
and all session state is kept client-side.
+---------+ +------------+
| | -> PUT /foo -> | |
| | -> Hello World! -> | |
| | | |
| | <- 201 CREATED <- | |
| | | |
| | -> GET /foo -> | |
| Browser | | Web Server |
| | <- 200 OK <- | |
| | <- Hello World! <- | |
| | | |
| | -> DELETE /foo -> | |
| | | |
| | <- 200 OK <- | |
+---------+ +------------+
While HTTP methods such as C<PUT>, C<GET> and C<DELETE> are not directly part of REST they go very well with it and are
commonly used to manipulate I<resources>.
=head2 Sessions
HTTP was designed as a stateless protocol, web servers don't know anything about previous requests, which makes
user-friendly login systems very tricky. Sessions solve this problem by allowing web applications to keep stateful
information across several HTTP requests.
GET /login?user=sebastian&pass=s3cret HTTP/1.1
Host: mojolicious.org
HTTP/1.1 200 OK
Set-Cookie: sessionid=987654321
Content-Length: 10
Hello sebastian.
GET /protected HTTP/1.1
Host: mojolicious.org
Cookie: sessionid=987654321
HTTP/1.1 200 OK
Set-Cookie: sessionid=987654321
Content-Length: 16
Hello again sebastian.
Traditionally all session data was stored on the server-side and only session ids were exchanged between browser and
web server in the form of cookies.
Set-Cookie: session=hmac-sha1(base64(json($session)))
In L<Mojolicious> however we are taking this concept one step further by storing everything JSON serialized and Base64
encoded in HMAC-SHA1 signed cookies, which is more compatible with the REST philosophy and reduces infrastructure
requirements.
=head2 Test-Driven Development
TDD is a software development process where the developer starts writing failing test cases that define the desired
functionality and then moves on to producing code that passes these tests. There are many advantages such as always
having good test coverage and code being designed for testability, which will in turn often prevent future changes from
breaking old code. Much of L<Mojolicious> was developed using TDD.
=head1 PROTOTYPE
One of the main differences between L<Mojolicious> and other web frameworks is that it also includes
L<Mojolicious::Lite>, a micro web framework optimized for rapid prototyping.
=head2 Differences
You likely know the feeling, you've got a really cool idea and want to try it as quickly as possible, that's exactly
why L<Mojolicious::Lite> applications don't need more than a single file.
myapp.pl # Templates and even static files can be inlined
Full L<Mojolicious> applications on the other hand are much closer to a well organized CPAN distribution to maximize
maintainability.
myapp # Application directory
|- script # Script directory
| +- my_app # Application script
|- lib # Library directory
| |- MyApp.pm # Application class
| +- MyApp # Application namespace
| +- Controller # Controller namespace
| +- Example.pm # Controller class
|- my_app.yml # Configuration file
|- t # Test directory
| +- basic.t # Random test
|- log # Log directory
| +- development.log # Development mode log file
|- public # Static file directory (served automatically)
| +- index.html # Static HTML file
+- templates # Template directory
|- layouts # Template directory for layouts
| +- default.html.ep # Layout template
+- example # Template directory for "Example" controller
+- welcome.html.ep # Template for "welcome" action
Both application skeletons can be automatically generated with the commands
L<Mojolicious::Command::Author::generate::lite_app> and L<Mojolicious::Command::Author::generate::app>.
$ mojo generate lite-app myapp.pl
$ mojo generate app MyApp
Feature-wise both are almost equal, the only real differences are organizational, so each one can be gradually
transformed into the other.
=head2 Foundation
We start our new application with a single executable Perl script.
$ mkdir myapp
$ cd myapp
$ touch myapp.pl
$ chmod 744 myapp.pl
This will be the foundation for our login manager example application.
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
get '/' => sub ($c) {
$c->render(text => 'Hello World!');
};
app->start;
The built-in development web server makes working on your application a lot of fun thanks to automatic reloading.
$ morbo ./myapp.pl
Web application available at http://127.0.0.1:3000
Just save your changes and they will be automatically in effect the next time you refresh your browser.
=head2 A bird's-eye view
It all starts with an HTTP request like this, sent by your browser.
GET / HTTP/1.1
Host: localhost:3000
Once the request has been received by the web server through the event loop, it will be passed on to L<Mojolicious>,
where it will be handled in a few simple steps.
=over 2
=item 1.
Check if a static file exists that would meet the requirements.
=item 2.
Try to find a route that would meet the requirements.
=item 3.
Dispatch the request to this route, usually reaching one or more actions.
=item 4.
Process the request, maybe generating a response with the renderer.
=item 5.
Return control to the web server, and if no response has been generated yet, wait for a non-blocking operation to do so
through the event loop.
=back
With our application the router would have found an action in step 2, and rendered some text in step 4, resulting in an
HTTP response like this being sent back to the browser.
HTTP/1.1 200 OK
Content-Length: 12
Hello World!
=head2 Model
In L<Mojolicious> we consider web applications simple frontends for existing business logic, that means L<Mojolicious>
is by design entirely I<model> layer agnostic and you just use whatever Perl modules you like most.
$ mkdir -p lib/MyApp/Model
$ touch lib/MyApp/Model/Users.pm
$ chmod 644 lib/MyApp/Model/Users.pm
Our login manager will simply use a plain old Perl module abstracting away all logic related to matching usernames and
passwords. The name C<MyApp::Model::Users> is an arbitrary choice, and is simply used to make the separation of
concerns more visible.
package MyApp::Model::Users;
use strict;
use warnings;
use experimental qw(signatures);
use Mojo::Util qw(secure_compare);
my $USERS = {
joel => 'las3rs',
marcus => 'lulz',
sebastian => 'secr3t'
};
sub new { bless {}, shift }
sub check ($self, $user, $pass) {
# Success
return 1 if $USERS->{$user} && secure_compare $USERS->{$user}, $pass;
# Fail
return undef;
}
1;
A simple helper can be registered with the function L<Mojolicious::Lite/"helper"> to make our model available to all
actions and templates.
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
use lib qw(lib);
use MyApp::Model::Users;
# Helper to lazy initialize and store our model object
helper users => sub { state $users = MyApp::Model::Users->new };
# /?user=sebastian&pass=secr3t
any '/' => sub ($c) {
# Query parameters
my $user = $c->param('user') || '';
my $pass = $c->param('pass') || '';
# Check password
return $c->render(text => "Welcome $user.") if $c->users->check($user, $pass);
# Failed
$c->render(text => 'Wrong username or password.');
};
app->start;
The method L<Mojolicious::Controller/"param"> is used to access query parameters, C<POST> parameters, file uploads and
route placeholders, all at once.
=head2 Testing
In L<Mojolicious> we take testing very serious and try to make it a pleasant experience.
$ mkdir t
$ touch t/login.t
$ chmod 644 t/login.t
L<Test::Mojo> is a scriptable HTTP user agent designed specifically for testing, with many fun state of the art
features such as CSS selectors based on L<Mojo::DOM>.
use Test::More;
use Test::Mojo;
# Include application
use Mojo::File qw(curfile);
require(curfile->dirname->sibling('myapp.pl'));
# Allow 302 redirect responses
my $t = Test::Mojo->new;
$t->ua->max_redirects(1);
# Test if the HTML login form exists
$t->get_ok('/')
->status_is(200)
->element_exists('form input[name="user"]')
->element_exists('form input[name="pass"]')
->element_exists('form input[type="submit"]');
# Test login with valid credentials
$t->post_ok('/' => form => {user => 'sebastian', pass => 'secr3t'})
->status_is(200)
->text_like('html body' => qr/Welcome sebastian/);
# Test accessing a protected page
$t->get_ok('/protected')->status_is(200)->text_like('a' => qr/Logout/);
# Test if HTML login form shows up again after logout
$t->get_ok('/logout')
->status_is(200)
->element_exists('form input[name="user"]')
->element_exists('form input[name="pass"]')
->element_exists('form input[type="submit"]');
done_testing();
Your application won't pass these tests, but from now on you can use them to check your progress.
$ prove -l
$ prove -l t/login.t
$ prove -l -v t/login.t
Or perform quick requests right from the command line with L<Mojolicious::Command::get>.
$ ./myapp.pl get /
Wrong username or password.
$ ./myapp.pl get -v '/?user=sebastian&pass=secr3t'
GET /?user=sebastian&pass=secr3t HTTP/1.1
User-Agent: Mojolicious (Perl)
Accept-Encoding: gzip
Content-Length: 0
Host: localhost:59472
HTTP/1.1 200 OK
Date: Sun, 18 Jul 2010 13:09:58 GMT
Server: Mojolicious (Perl)
Content-Length: 12
Content-Type: text/plain
Welcome sebastian.
=head2 State keeping
Sessions in L<Mojolicious> pretty much just work out of the box once you start using the method
L<Mojolicious::Controller/"session">, there is no setup required, but we suggest setting a more secure passphrase with
L<Mojolicious/"secrets">.
$app->secrets(['Mojolicious rocks']);
This passphrase is used by the HMAC-SHA1 algorithm to make signed cookies tamper resistant and can be changed at any
time to invalidate all existing sessions.
$c->session(user => 'sebastian');
my $user = $c->session('user');
By default all sessions expire after one hour, for more control you can use the C<expiration> session value to set an
expiration date in seconds from now.
$c->session(expiration => 3600);
And the whole session can be deleted by using the C<expires> session value to set an absolute expiration date in the
past.
$c->session(expires => 1);
For data that should only be visible on the next request, like a confirmation message after a C<302> redirect performed
with L<Mojolicious::Plugin::DefaultHelpers/"redirect_to">, you can use the flash, accessible through
L<Mojolicious::Plugin::DefaultHelpers/"flash">.
$c->flash(message => 'Everything is fine.');
$c->redirect_to('goodbye');
Just remember that all session data gets serialized with L<Mojo::JSON> and stored in HMAC-SHA1 signed cookies, which
usually have a C<4096> byte (4KiB) limit, depending on browser.
=head2 Final prototype
A final C<myapp.pl> prototype passing all of the tests above could look like this.
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
use lib qw(lib);
use MyApp::Model::Users;
# Make signed cookies tamper resistant
app->secrets(['Mojolicious rocks']);
helper users => sub { state $users = MyApp::Model::Users->new };
# Main login action
any '/' => sub ($c) {
# Query or POST parameters
my $user = $c->param('user') || '';
my $pass = $c->param('pass') || '';
# Check password and render "index.html.ep" if necessary
return $c->render unless $c->users->check($user, $pass);
# Store username in session
$c->session(user => $user);
# Store a friendly message for the next page in flash
$c->flash(message => 'Thanks for logging in.');
# Redirect to protected page with a 302 response
$c->redirect_to('protected');
} => 'index';
# Make sure user is logged in for actions in this group
group {
under sub ($c) {
# Redirect to main page with a 302 response if user is not logged in
return 1 if $c->session('user');
$c->redirect_to('index');
return undef;
};
# A protected page auto rendering "protected.html.ep"
get '/protected';
};
# Logout action
get '/logout' => sub ($c) {
# Expire and in turn clear session automatically
$c->session(expires => 1);
# Redirect to main page with a 302 response
$c->redirect_to('index');
};
app->start;
__DATA__
@@ index.html.ep
% layout 'default';
%= form_for index => begin
% if (param 'user') {
<b>Wrong name or password, please try again.</b><br>
% }
Name:<br>
%= text_field 'user'
<br>Password:<br>
%= password_field 'pass'
<br>
%= submit_button 'Login'
% end
@@ protected.html.ep
% layout 'default';
% if (my $msg = flash 'message') {
<b><%= $msg %></b><br>
% }
Welcome <%= session 'user' %>.<br>
%= link_to Logout => 'logout'
@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
<head><title>Login Manager</title></head>
<body><%= content %></body>
</html>
And the directory structure should be looking like this now.
myapp
|- myapp.pl
|- lib
| +- MyApp
| +- Model
| +- Users.pm
+- t
+- login.t
Our templates are using quite a few features of the renderer, L<Mojolicious::Guides::Rendering> explains them all in
great detail.
=head1 WELL-STRUCTURED APPLICATION
Due to the flexibility of L<Mojolicious> there are many variations of the actual growing process, but this should give
you a good overview of the possibilities.
=head2 Inflating templates
All templates and static files inlined in the C<DATA> section can be automatically turned into separate files in the
C<templates> and C<public> directories with the command L<Mojolicious::Command::Author::inflate>.
$ ./myapp.pl inflate
Those directories have a higher precedence, so inflating can also be a great way to allow your users to customize their
applications.
=head2 Simplified application class
This is the heart of every full L<Mojolicious> application and always gets instantiated during server startup.
$ touch lib/MyApp.pm
$ chmod 644 lib/MyApp.pm
We will start by extracting all actions from C<myapp.pl> and turn them into simplified hybrid routes in the
L<Mojolicious::Routes> router, none of the actual action code needs to be changed.
package MyApp;
use Mojo::Base 'Mojolicious', -signatures;
use MyApp::Model::Users;
sub startup ($self) {
$self->secrets(['Mojolicious rocks']);
$self->helper(users => sub { state $users = MyApp::Model::Users->new });
my $r = $self->routes;
$r->any('/' => sub ($c) {
my $user = $c->param('user') || '';
my $pass = $c->param('pass') || '';
return $c->render unless $c->users->check($user, $pass);
$c->session(user => $user);
$c->flash(message => 'Thanks for logging in.');
$c->redirect_to('protected');
} => 'index');
my $logged_in = $r->under(sub ($c) {
return 1 if $c->session('user');
$c->redirect_to('index');
return undef;
});
$logged_in->get('/protected');
$r->get('/logout' => sub ($c) {
$c->session(expires => 1);
$c->redirect_to('index');
});
}
1;
The C<startup> method gets called right after instantiation and is the place where the whole application gets set up.
Since full L<Mojolicious> applications can use nested routes they have no need for C<group> blocks.
=head2 Simplified application script
C<myapp.pl> itself can now be turned into a simplified application script to allow running tests again.
#!/usr/bin/env perl
use Mojo::Base -strict;
use lib qw(lib);
use Mojolicious::Commands;
# Start command line interface for application
Mojolicious::Commands->start_app('MyApp');
And the directory structure of our hybrid application should be looking like this.
myapp
|- myapp.pl
|- lib
| |- MyApp.pm
| +- MyApp
| +- Model
| +- Users.pm
|- t
| +- login.t
+- templates
|- layouts
| +- default.html.ep
|- index.html.ep
+- protected.html.ep
=head2 Controller class
Hybrid routes are a nice intermediate step, but to maximize maintainability it makes sense to split our action code
from its routing information.
$ mkdir lib/MyApp/Controller
$ touch lib/MyApp/Controller/Login.pm
$ chmod 644 lib/MyApp/Controller/Login.pm
Once again the actual action code does not need to change, we just rename C<$c> to C<$self> since the controller is now
the invocant.
package MyApp::Controller::Login;
use Mojo::Base 'Mojolicious::Controller', -signatures;
sub index ($self) {
my $user = $self->param('user') || '';
my $pass = $self->param('pass') || '';
return $self->render unless $self->users->check($user, $pass);
$self->session(user => $user);
$self->flash(message => 'Thanks for logging in.');
$self->redirect_to('protected');
}
sub logged_in ($self) {
return 1 if $self->session('user');
$self->redirect_to('index');
return undef;
}
sub logout ($self) {
$self->session(expires => 1);
$self->redirect_to('index');
}
1;
All L<Mojolicious::Controller> controllers are plain old Perl classes and get instantiated on demand.
=head2 Application class
The application class C<lib/MyApp.pm> can now be reduced to model and routing information.
package MyApp;
use Mojo::Base 'Mojolicious', -signatures;
use MyApp::Model::Users;
sub startup ($self) {
$self->secrets(['Mojolicious rocks']);
$self->helper(users => sub { state $users = MyApp::Model::Users->new });
my $r = $self->routes;
$r->any('/')->to('login#index')->name('index');
my $logged_in = $r->under('/')->to('login#logged_in');
$logged_in->get('/protected')->to('login#protected');
$r->get('/logout')->to('login#logout');
}
1;
The router allows many different route variations, L<Mojolicious::Guides::Routing> explains them all in great detail.
=head2 Templates
Templates are our views, and usually bound to controllers, so they need to be moved into the appropriate directories.
$ mkdir templates/login
$ mv templates/index.html.ep templates/login/index.html.ep
$ mv templates/protected.html.ep templates/login/protected.html.ep
=head2 Script
Finally C<myapp.pl> can be moved into a C<script> directory and renamed to C<my_app> to follow the CPAN standard.
$ mkdir script
$ mv myapp.pl script/my_app
Just a few small details change, instead of a relative path to L<lib> we now use L<Mojo::File> to get an absolute path,
allowing us to start the application from outside its home directory.
#!/usr/bin/env perl
use strict;
use warnings;
use Mojo::File qw(curfile);
use lib curfile->dirname->sibling('lib')->to_string;
use Mojolicious::Commands;
# Start command line interface for application
Mojolicious::Commands->start_app('MyApp');
=head2 Simplified tests
Full L<Mojolicious> applications are a little easier to test, so C<t/login.t> can be simplified.
use Test::More;
use Test::Mojo;
# Load application class
my $t = Test::Mojo->new('MyApp');
$t->ua->max_redirects(1);
$t->get_ok('/')
->status_is(200)
->element_exists('form input[name="user"]')
->element_exists('form input[name="pass"]')
->element_exists('form input[type="submit"]');
$t->post_ok('/' => form => {user => 'sebastian', pass => 'secr3t'})
->status_is(200)
->text_like('html body' => qr/Welcome sebastian/);
$t->get_ok('/protected')->status_is(200)->text_like('a' => qr/Logout/);
$t->get_ok('/logout')
->status_is(200)
->element_exists('form input[name="user"]')
->element_exists('form input[name="pass"]')
->element_exists('form input[type="submit"]');
done_testing();
And our final directory structure should be looking like this.
myapp
|- script
| +- my_app
|- lib
| |- MyApp.pm
| +- MyApp
| |- Controller
| | +- Login.pm
| +- Model
| +- Users.pm
|- t
| +- login.t
+- templates
|- layouts
| +- default.html.ep
+- login
|- index.html.ep
+- protected.html.ep
Test-driven development takes a little getting used to, but can be a very powerful tool.
=head1 MORE
You can continue with L<Mojolicious::Guides> now or take a look at the L<Mojolicious
wiki|https://github.com/mojolicious/mojo/wiki>, which contains a lot more documentation and examples by many different
authors.
=head1 SUPPORT
If you have any questions the documentation might not yet answer, don't hesitate to ask in the
L<Forum|https://forum.mojolicious.org> or the official IRC channel C<#mojo> on C<chat.freenode.net>
(L<chat now!|https://webchat.freenode.net/#mojo>).
=cut

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,644 @@
=encoding utf8
=head1 NAME
Mojolicious::Guides::Testing - Web Application Testing Made Easy
=head1 OVERVIEW
This document is an introduction to testing web applications with L<Test::Mojo>. L<Test::Mojo> can be thought of as a
module that provides all of the tools and testing assertions needed to test web applications in a Perl-ish way.
While L<Test::Mojo> can be used to test any web application, it has shortcuts designed to make testing L<Mojolicious>
web applications easy and pain-free.
Please refer to the L<Test::Mojo> documentation for a complete reference to many of the ideas and syntax introduced in
this document.
A test file for a simple web application might look like:
use Mojo::Base -strict;
use Test::Mojo;
use Test::More;
# Start a Mojolicious app named "Celestial"
my $t = Test::Mojo->new('Celestial');
# Post a JSON document
$t->post_ok('/notifications' => json => {event => 'full moon'})
->status_is(201)
->json_is('/message' => 'notification created');
# Perform GET requests and look at the responses
$t->get_ok('/sunrise')
->status_is(200)
->content_like(qr/ am$/);
$t->get_ok('/sunset')
->status_is(200)
->content_like(qr/ pm$/);
# Post a URL-encoded form
$t->post_ok('/insurance' => form => {name => 'Jimmy', amount => '€3.000.000'})
->status_is(200);
# Use Test::More's like() to check the response
like $t->tx->res->dom->at('div#thanks')->text, qr/thank you/, 'thanks';
done_testing();
In the rest of this document we'll explore these concepts and others related to L<Test::Mojo>.
=head1 CONCEPTS
Essentials every L<Mojolicious> developer should know.
=head2 L<Test::Mojo> at a glance
The L<Test::More> module bundled with Perl includes several primitive test assertions, such as C<ok>, C<is>, C<isnt>,
C<like>, C<unlike>, C<cmp_ok>, etc. An assertion "passes" if its expression returns a true value. The assertion method
prints "ok" or "not ok" if an assertion passes or fails (respectively).
L<Test::Mojo> supplies additional test assertions organized around the web application request/response transaction
(transport, response headers, response bodies, etc.), and WebSocket communications.
One interesting thing of note: the return value of L<Test::Mojo> object assertions is always the test object itself,
allowing us to "chain" test assertion methods. So rather than grouping related test statements like this:
$t->get_ok('/frogs');
$t->status_is(200);
$t->content_like(qr/bullfrog/);
$t->content_like(qr/hypnotoad/);
Method chaining allows us to connect test assertions that belong together:
$t->get_ok('/frogs')
->status_is(200)
->content_like(qr/bullfrog/)
->content_like(qr/hypnotoad/);
This makes for a much more I<concise> and I<coherent> testing experience: concise because we are not repeating the
invocant for each test, and coherent because assertions that belong to the same request are syntactically bound in the
same method chain.
Occasionally it makes sense to break up a test to perform more complex assertions on a response. L<Test::Mojo> exposes
the entire transaction object so you can get all the data you need from a response:
$t->put_ok('/bees' => json => {type => 'worker', name => 'Karl'})
->status_is(202)
->json_has('/id');
# Pull out the id from the response
my $newbee = $t->tx->res->json('/id');
# Make a new request with data from the previous response
$t->get_ok("/bees/$newbee")
->status_is(200)
->json_is('/name' => 'Karl');
The L<Test::Mojo> object is I<stateful>. As long as we haven't started a new transaction by invoking one of the C<*_ok>
methods, the request and response objects from the previous transaction are available in the L<Test::Mojo> object:
# First transaction
$t->get_ok('/frogs?q=bullfrog' => {'Content-Type' => 'application/json'})
->status_is(200)
->json_like('/0/species' => qr/catesbeianus/i);
# Still first transaction
$t->content_type_is('application/json');
# Second transaction
$t->get_ok('/frogs?q=banjo' => {'Content-Type' => 'text/html'})
->status_is(200)
->content_like(qr/interioris/i);
# Still second transaction
$t->content_type_is('text/html');
This statefulness also enables L<Test::Mojo> to handle sessions, follow redirects, and inspect past responses during a
redirect.
=head2 The L<Test::Mojo> object
The L<Test::Mojo> object manages the Mojolicious application lifecycle (if a Mojolicious application class is supplied)
as well as exposes the built-in L<Mojo::UserAgent> object. To create a bare L<Test::Mojo> object:
my $t = Test::Mojo->new;
This object initializes a L<Mojo::UserAgent> object and provides a variety of test assertion methods for accessing a
web application. For example, with this object, we could test any running web application:
$t->get_ok('https://www.google.com/')
->status_is(200)
->content_like(qr/search/i);
You can access the user agent directly if you want to make web requests without triggering test assertions:
my $tx = $t->ua->post('https://duckduckgo.com/html' => form => {q => 'hypnotoad'});
$tx->result->dom->find('a.result__a')->each(sub { say $_->text });
See L<Mojo::UserAgent> for the complete API and return values.
=head2 Testing Mojolicious applications
If you pass the name of a L<Mojolicious> application class (e.g., 'MyApp') to the L<Test::Mojo> constructor,
L<Test::Mojo> will instantiate the class and start it, and cause it to listen on a random (unused) port number. Testing
a Mojolicious application using L<Test::Mojo> will never conflict with running applications, including the application
you're testing.
The L<Mojo::UserAgent> object in L<Test::Mojo> will know where the application is running and make requests to it. Once
the tests have completed, the L<Mojolicious> application will be torn down.
# Listens on localhost:32114 (some unused TCP port)
my $t = Test::Mojo->new('Frogs');
This object initializes a L<Mojo::UserAgent> object, loads the Mojolicious application C<Frogs>, binds and listens on a
free TCP port (e.g., 32114), and starts the application event loop. When the L<Test::Mojo> object (C<$t>) goes out of
scope, the application is stopped.
Relative URLs in the test object method assertions (C<get_ok>, C<post_ok>, etc.) will be sent to the Mojolicious
application started by L<Test::Mojo>:
# Rewritten to "http://localhost:32114/frogs"
$t->get_ok('/frogs');
L<Test::Mojo> has a lot of handy shortcuts built into it to make testing L<Mojolicious> or L<Mojolicious::Lite>
applications enjoyable.
=head3 An example
Let's spin up a Mojolicious application using C<mojo generate app MyApp>. The C<mojo> utility will create a working
application and a C<t> directory with a working test file:
$ mojo generate app MyApp
[mkdir] /my_app/script
[write] /my_app/script/my_app
[chmod] /my_app/script/my_app 744
...
[mkdir] /my_app/t
[write] /my_app/t/basic.t
...
Let's run the tests (we'll create the C<log> directory to quiet the application output):
$ cd my_app
$ mkdir log
$ prove -lv t
t/basic.t ..
ok 1 - GET /
ok 2 - 200 OK
ok 3 - content is similar
1..3
ok
All tests successful.
Files=1, Tests=3, 0 wallclock secs ( 0.03 usr 0.01 sys + 0.33 cusr 0.07 csys = 0.44 CPU)
Result: PASS
The boilerplate test file looks like this:
use Mojo::Base -strict;
use Test::More;
use Test::Mojo;
my $t = Test::Mojo->new('MyApp');
$t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);
done_testing();
Here we can see our application class name C<MyApp> is passed to the L<Test::Mojo> constructor. Under the hood,
L<Test::Mojo> creates a new L<Mojo::Server> instance, loads C<MyApp> (which we just created), and runs the application.
We write our tests with relative URLs because L<Test::Mojo> takes care of getting the request to the running test
application (since its port may change between runs).
=head3 Testing with configuration data
We can alter the behavior of our application using environment variables (such as C<MOJO_MODE>) and through
configuration values. One nice feature of L<Test::Mojo> is its ability to pass configuration values directly from its
constructor.
Let's modify our application and add a "feature flag" to enable a new feature when the C<enable_weather> configuration
value is set:
# Load configuration from hash returned by "my_app.conf"
my $config = $self->plugin('Config');
# Normal route to controller
$r->get('/')->to('example#welcome');
# NEW: this route only exists if "enable_weather" is set in the configuration
if ($config->{enable_weather}) {
$r->get('/weather' => sub ($c) {
$c->render(text => "It's hot! 🔥");
});
}
To test this new feature, we don't even need to create a configuration file—we can simply pass the configuration data
to the application directly via L<Test::Mojo>'s constructor:
my $t = Test::Mojo->new(MyApp => {enable_weather => 1});
$t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);
$t->get_ok('/weather')->status_is(200)->content_like(qr/🔥/);
When we run these tests, L<Test::Mojo> will pass this configuration data to the application, which will cause it to
create a special C</weather> route that we can access in our tests. Unless C<enable_weather> is set in a configuration
file, this route will not exist when the application runs. Feature flags like this allow us to do soft rollouts of
features, targeting a small audience for a period of time. Once the feature has been proven, we can refactor the
conditional and make it a full release.
This example shows how easy it is to start testing a Mojolicious application and how to set specific application
configuration directives from a test file.
=head3 Testing application helpers
Let's say we register a helper in our application to generate an HTTP Basic Authorization header:
use Mojo::Util qw(b64_encode);
app->helper(basic_auth => sub ($c, @values) {
return {Authorization => 'Basic ' . b64_encode join(':' => @values), ''};
});
How do we test application helpers like this? L<Test::Mojo> has access to the application object, which allows us to
invoke helpers from our test file:
my $t = Test::Mojo->new('MyApp');
is_deeply $t->app->basic_auth(bif => "Bif's Passwerdd"), {Authorization => 'Basic YmlmOkJpZidzIFBhc3N3ZXJkZA=='},
'correct header value';
Any aspect of the application (helpers, plugins, routes, etc.) can be introspected from L<Test::Mojo> through the
application object. This enables us to get deep test coverage of L<Mojolicious>-based applications.
=head1 ASSERTIONS
This section describes the basic test assertions supplied by L<Test::Mojo>. There are four broad categories of
assertions for HTTP requests:
=over 2
=item * HTTP requests
=item * HTTP response status
=item * HTTP response headers
=item * HTTP response content/body
=back
WebSocket test assertions are covered in L</Testing WebSocket web services>.
=head2 HTTP request assertions
L<Test::Mojo> has a L<Mojo::UserAgent> object that allows it to make HTTP requests and check for HTTP transport errors.
HTTP request assertions include C<get_ok>, C<post_ok>, etc. These assertions do not test whether the request was
handled I<successfully>, only that the web application handled the request in an HTTP compliant way.
You may also make HTTP requests using custom verbs (beyond C<GET>, C<POST>, C<PUT>, etc.) by building your own
transaction object. See L</"Custom transactions"> below.
=head3 Using HTTP request assertions
To post a URL-encoded form to the C</calls> endpoint of an application, we simply use the C<form> content type
shortcut:
$t->post_ok('/calls' => form => {to => '+43.55.555.5555'});
Which will create the following HTTP request:
POST /calls HTTP/1.1
Content-Length: 20
Content-Type: application/x-www-form-urlencoded
to=%2B43.55.555.5555
The C<*_ok> HTTP request assertion methods accept the same arguments as their corresponding L<Mojo::UserAgent> methods
(except for the callback argument). This allows us to set headers and build query strings for authentic test
situations:
$t->get_ok('/internal/personnel' => {Authorization => 'Token secret-password'} => form => {q => 'Professor Plum'});
which generates the following request:
GET /internal/personnel?q=Professor+Plum HTTP/1.1
Content-Length: 0
Authorization: Token secret-password
The C<form> content generator (see L<Mojo::UserAgent::Transactor>) will generate a query string for C<GET> requests and
C<application/x-www-form-urlencoded> or C<multipart/form-data> for POST requests.
While these C<*_ok> assertions make the HTTP I<requests> we expect, they tell us little about I<how well> the
application handled the request. The application we're testing might have returned any content-type, body, or HTTP
status code (200, 302, 400, 404, 500, etc.) and we wouldn't know it.
L<Test::Mojo> provides assertions to test almost every aspect of the HTTP response, including the HTTP response status
code, the value of the C<Content-Type> header, and other arbitrary HTTP header information.
=head2 HTTP response status code
While not technically an HTTP header, the status line is the first line in an HTTP response and is followed by the
response headers. Testing the response status code is common in REST-based and other web applications that use the HTTP
status codes to broadly indicate the type of response the server is returning.
Testing the status code is as simple as adding the C<status_is> assertion:
$t->post_ok('/doorbell' => form => {action => 'ring once'})
->status_is(200);
Along with C<status_isnt>, this will cover most needs. For more elaborate status code testing, you can access the
response internals directly:
$t->post_ok('/doorbell' => form => {action => 'ring once'});
is $t->tx->res->message, 'Moved Permanently', 'try next door';
=head2 HTTP response headers
L<Test::Mojo> allows us to inspect and make assertions about HTTP response headers. The C<Content-Type> header is
commonly tested and has its own assertion:
$t->get_ok('/map-of-the-world.pdf')
->content_type_is('application/pdf');
This is equivalent to the more verbose:
$t->get_ok('/map-of-the-world.pdf')
->header_is('Content-Type' => 'application/pdf');
We can test for multiple headers in a single response using method chains:
$t->get_ok('/map-of-the-world.pdf')
->content_type_is('application/pdf')
->header_isnt('Compression' => 'gzip')
->header_unlike('Server' => qr/IIS/i);
=head2 HTTP response content assertions
L<Test::Mojo> also exposes a rich set of assertions for testing the body of a response, whether that body be HTML,
plain-text, or JSON. The C<content_*> methods look at the body of the response as plain text (as defined by the
response's character set):
$t->get_ok('/scary-things/spiders.json')
->content_is('{"arachnid":"brown recluse"}');
Although this is a JSON document, C<content_is> treats it as if it were a text document. This may be useful for
situations where we're looking for a particular string and not concerned with the structure of the document. For
example, we can do the same thing with an HTML document:
$t->get_ok('/scary-things/spiders.html')
->content_like(qr{<title>All The Spiders</title>});
But because L<Test::Mojo> has access to everything that L<Mojo::UserAgent> does, we can introspect JSON documents as
well as DOM-based documents (HTML, XML) with assertions that allow us to check for the existence of elements as well as
inspect the content of text nodes.
=head3 JSON response assertions
L<Test::Mojo>'s L<Mojo::UserAgent> has access to a JSON parser, which allows us to test to see if a JSON response
contains a value at a location in the document using JSON pointer syntax:
$t->get_ok('/animals/friendly.json')
->json_has('/beings/jeremiah/age');
This assertion tells us that the C<friendly.json> document contains a value at the C</beings/jeremiah/age> JSON pointer
location. We can also inspect the value at JSON pointer locations:
$t->get_ok('/animals/friendly.json')
->json_has('/beings/jeremiah/age')
->json_is('/beings/jeremiah/age' => 42)
->json_like('/beings/jeremiah/species' => qr/bullfrog/i);
JSON pointer syntax makes testing JSON responses simple and readable.
=head3 DOM response assertions
We can also inspect HTML and XML responses using the L<Mojo::DOM> parser in the user agent. Here are a few examples
from the L<Test::Mojo> documentation:
$t->text_is('div.foo[x=y]' => 'Hello!');
$t->text_is('html head title' => 'Hello!', 'right title');
The L<Mojo::DOM> parser uses the CSS selector syntax described in L<Mojo::DOM::CSS>, allowing us to test for values in
HTML and XML documents without resorting to typically verbose and inflexible DOM traversal methods.
=head1 ADVANCED TOPICS
This section describes some complex (but common) testing situations that L<Test::Mojo> excels in making simple.
=head2 Redirects
The L<Mojo::UserAgent> object in L<Test::Mojo> can handle HTTP redirections internally to whatever level you need.
Let's say we have a web service that redirects C</1> to C</2>, C</2> redirects to C</3>, C</3> redirects to C</4>, and
C</4> redirects to C</5>:
GET /1
returns:
302 Found
Location: /2
and:
GET /2
returns:
302 Found
Location: /3
and so forth, up to C</5>:
GET /5
which returns the data we wanted:
200 OK
{"message":"this is five"}
We can tell the user agent in L<Test::Mojo> how to deal with redirects. Each test is making a request to C<GET /1>, but
we vary the number of redirects the user agent should follow with each test:
my $t = Test::Mojo->new;
$t->get_ok('/1')
->header_is(location => '/2');
$t->ua->max_redirects(1);
$t->get_ok('/1')
->header_is(location => '/3');
$t->ua->max_redirects(2);
$t->get_ok('/1')
->header_is(location => '/4');
# Look at the previous hop
is $t->tx->previous->res->headers->location, '/3', 'previous redirect';
$t->ua->max_redirects(3);
$t->get_ok('/1')
->header_is(location => '/5');
$t->ua->max_redirects(4);
$t->get_ok('/1')
->json_is('/message' => 'this is five');
When we set C<max_redirects>, it stays set for the life of the test object until we change it.
L<Test::Mojo>'s handling of HTTP redirects eliminates the need for making many, sometimes an unknown number, of
redirections to keep testing precise and easy to follow (ahem).
=head2 Cookies and session management
We can use L<Test::Mojo> to test applications that keep session state in cookies. By default, the L<Mojo::UserAgent>
object in L<Test::Mojo> will manage session for us by saving and sending cookies automatically, just like common web
browsers:
use Mojo::Base -strict;
use Test::More;
use Test::Mojo;
my $t = Test::Mojo->new('MyApp');
# No authorization cookie
$t->get_ok('/')
->status_is(401)
->content_is('Please log in');
# Application sets an authorization cookie
$t->post_ok('/login' => form => {password => 'let me in'})
->status_is(200)
->content_is('You are logged in');
# Sends the cookie from the previous transaction
$t->get_ok('/')
->status_is(200)
->content_like(qr/You logged in at \d+/);
# Clear the cookies
$t->reset_session;
# No authorization cookie again
$t->get_ok('/')
->status_is(401)
->content_is('Please log in');
We can also inspect cookies in responses for special values through the transaction's response
(L<Mojo::Message::Response>) object:
$t->get_ok('/');
like $t->tx->res->cookie('smarty'), qr/smarty=pants/, 'cookie found';
=head2 Custom transactions
Let's say we have an application that responds to a new HTTP verb C<RING> and to use it we must also pass in a secret
cookie value. This is not a problem. We can test the application by creating a L<Mojo::Transaction> object, setting the
cookie (see L<Mojo::Message::Request>), then passing the transaction object to C<request_ok>:
# Use custom "RING" verb
my $tx = $t->ua->build_tx(RING => '/doorbell');
# Set a special cookie
$tx->req->cookies({name => 'Secret', value => "don't tell anybody"});
# Make the request
$t->request_ok($tx)
->status_is(200)
->json_is('/status' => 'ding dong');
=head2 Testing WebSocket web services
While the message flow on WebSocket connections can be rather dynamic, it more often than not is quite predictable,
which allows this rather pleasant L<Test::Mojo> WebSocket API to be used:
use Mojo::Base -strict;
use Test::More;
use Test::Mojo;
# Test echo web service
my $t = Test::Mojo->new('EchoService');
$t->websocket_ok('/echo')
->send_ok('Hello Mojo!')
->message_ok
->message_is('echo: Hello Mojo!')
->finish_ok;
# Test JSON web service
$t->websocket_ok('/echo.json')
->send_ok({json => {test => [1, 2, 3]}})
->message_ok
->json_message_is('/test' => [1, 2, 3])
->finish_ok;
done_testing();
Because of their inherent asynchronous nature, testing WebSocket communications can be tricky. The L<Test::Mojo>
WebSocket assertions serialize messages via event loop primitives. This enables us to treat WebSocket messages as if
they were using the same request-response communication pattern we're accustomed to with HTTP.
To illustrate, let's walk through these tests. In the first test, we use the C<websocket_ok> assertion to ensure that
we can connect to our application's WebSocket route at C</echo> and that it's "speaking" WebSocket protocol to us. The
next C<send_ok> assertion tests the connection again (in case it closed, for example) and attempts to send the message
C<Hello Mojo!>. The next assertion, C<message_ok>, blocks (using the L<Mojo::IOLoop> singleton in the application) and
waits for a response from the server. The response is then compared with C<'echo: Hello Mojo!'> in the C<message_is>
assertion, and finally we close and test our connection status again with C<finish_ok>.
The second test is like the first, but now we're sending and expecting JSON documents at C</echo.json>. In the
C<send_ok> assertion we take advantage of L<Mojo::UserAgent>'s JSON content generator (see
L<Mojo::UserAgent::Transactor>) to marshal hash and array references into JSON documents, and then send them as a
WebSocket message. We wait (block) for a response from the server with C<message_ok>. Then because we're expecting a
JSON document back, we can leverage C<json_message_ok> which parses the WebSocket response body and returns an object
we can access through L<Mojo::JSON::Pointer> syntax. Then we close (and test) our WebSocket connection.
Testing WebSocket servers does not get any simpler than with L<Test::Mojo>.
=head2 Extending L<Test::Mojo>
If you see that you're writing a lot of test assertions that aren't chainable, you may benefit from writing your own
test assertions. Let's say we want to test the C<Location> header after a redirect. We'll create a new class with
L<Role::Tiny> that implements a test assertion named C<location_is>:
package Test::Mojo::Role::Location;
use Mojo::Base -role, -signatures;
sub location_is ($self, $value, $desc = "Location: $value") {
return $self->test('is', $self->tx->res->headers->location, $value, $desc);
}
1;
When we make new test assertions using roles, we want to use method signatures that match other C<*_is> methods in
L<Test::Mojo>, so here we accept the test object, the value to compare, and an optional description.
We assign a default description value (C<$desc>), then we use L<Test::Mojo/"test"> to compare the location header with
the expected header value, and finally propagates the L<Test::Mojo> object for method chaining.
With this new package, we're ready to compose a new test object that uses the role:
my $t = Test::Mojo->with_roles('+Location')->new('MyApp');
$t->post_ok('/redirect/mojo' => json => {message => 'Mojo, here I come!'})
->status_is(302)
->location_is('http://mojolicious.org')
->or(sub { diag 'I miss tempire.' });
In this section we've covered how to add custom test assertions to L<Test::Mojo> with roles and how to use those roles
to simplify testing.
=head1 MORE
You can continue with L<Mojolicious::Guides> now or take a look at the L<Mojolicious
wiki|https://github.com/mojolicious/mojo/wiki>, which contains a lot more documentation and examples by many different
authors.
=head1 SUPPORT
If you have any questions the documentation might not yet answer, don't hesitate to ask in the
L<Forum|https://forum.mojolicious.org> or the official IRC channel C<#mojo> on C<chat.freenode.net>
(L<chat now!|https://webchat.freenode.net/#mojo>).
=cut

View File

@@ -0,0 +1,956 @@
=encoding utf8
=head1 NAME
Mojolicious::Guides::Tutorial - Get started with Mojolicious
=head1 TUTORIAL
A quick example-driven introduction to the wonders of L<Mojolicious::Lite>. Almost everything you'll learn here also
applies to full L<Mojolicious> applications.
This is only the first of the L<Mojolicious::Guides>. Other guides delve deeper into topics like
L<growing|Mojolicious::Guides::Growing> a L<Mojolicious::Lite> prototype into a well-structured L<Mojolicious>
application, L<routing|Mojolicious::Guides::Routing>, L<rendering|Mojolicious::Guides::Rendering> and more. It is
highly encouraged that readers continue on to the remaining guides after reading this one.
=head2 Hello World
A simple Hello World application can look like this, L<strict>, L<warnings>, L<utf8> and Perl 5.16 L<features|feature>
are automatically enabled and a few L<functions|Mojolicious::Lite/"FUNCTIONS"> imported, when you use
L<Mojolicious::Lite>, turning your script into a full featured web application.
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
get '/' => sub ($c) {
$c->render(text => 'Hello World!');
};
app->start;
With L<Mojolicious::Command::Author::generate::lite_app> there is also a helper command to generate a small example
application.
$ mojo generate lite-app myapp.pl
=head2 Commands
Many different L<commands|Mojolicious::Commands/"COMMANDS"> are automatically available from the command line. CGI and
L<PSGI> environments can even be detected and will usually just work without commands.
$ ./myapp.pl daemon
Web application available at http://127.0.0.1:3000
$ ./myapp.pl daemon -l http://*:8080
Web application available at http://127.0.0.1:8080
$ ./myapp.pl cgi
...CGI output...
$ ./myapp.pl get /
Hello World!
$ ./myapp.pl
...List of available commands (or automatically detected environment)...
A call to L<Mojolicious/"start"> (C<app-E<gt>start>), which starts the command system, should be the last expression in
your application, because its return value can be significant.
# Use @ARGV to pick a command
app->start;
# Start the "daemon" command
app->start('daemon', '-l', 'http://*:8080');
=head2 Reloading
Your application will automatically reload itself if you start it with the L<morbo> development web server, so you
don't have to restart the server after every change.
$ morbo ./myapp.pl
Web application available at http://127.0.0.1:3000
For more information about how to deploy your application see also L<Mojolicious::Guides::Cookbook/"DEPLOYMENT">.
=head2 Routes
Routes are basically just fancy paths that can contain different kinds of placeholders and usually lead to an action,
if they match the path part of the request URL. The first argument passed to all actions (C<$c>) is a
L<Mojolicious::Controller> object, containing both the HTTP request and response.
use Mojolicious::Lite -signatures;
# Route leading to an action that renders some text
get '/foo' => sub ($c) {
$c->render(text => 'Hello World!');
};
app->start;
Response content is often generated by actions with L<Mojolicious::Controller/"render">, but more about that later.
=head2 GET/POST parameters
All C<GET> and C<POST> parameters sent with the request are accessible via L<Mojolicious::Controller/"param">.
use Mojolicious::Lite -signatures;
# /foo?user=sri
get '/foo' => sub ($c) {
my $user = $c->param('user');
$c->render(text => "Hello $user.");
};
app->start;
=head2 Stash and templates
The L<Mojolicious::Controller/"stash"> is used to pass data to templates, which can be inlined in the C<DATA> section.
A few stash values like C<template>, C<text> and C<data> are reserved and will be used by
L<Mojolicious::Controller/"render"> to decide how a response should be generated.
use Mojolicious::Lite -signatures;
# Route leading to an action that renders a template
get '/foo' => sub ($c) {
$c->stash(one => 23);
$c->render(template => 'magic', two => 24);
};
app->start;
__DATA__
@@ magic.html.ep
The magic numbers are <%= $one %> and <%= $two %>.
For more information about templates see also L<Mojolicious::Guides::Rendering/"Embedded Perl">.
=head2 HTTP
L<Mojolicious::Controller/"req"> and L<Mojolicious::Controller/"res"> give you full access to all HTTP features and
information.
use Mojolicious::Lite -signatures;
# Access request information
get '/agent' => sub ($c) {
my $host = $c->req->url->to_abs->host;
my $ua = $c->req->headers->user_agent;
$c->render(text => "Request by $ua reached $host.");
};
# Echo the request body and send custom header with response
post '/echo' => sub ($c) {
$c->res->headers->header('X-Bender' => 'Bite my shiny metal ass!');
$c->render(data => $c->req->body);
};
app->start;
You can test the more advanced examples right from the command line with L<Mojolicious::Command::get>.
$ ./myapp.pl get -v -M POST -c 'test' /echo
=head2 JSON
JSON is the most commonly used data-interchange format for web services. L<Mojolicious> loves JSON and comes with the
possibly fastest pure-Perl implementation L<Mojo::JSON> built right in, which is accessible through
L<Mojo::Message/"json"> as well as the reserved stash value C<json>.
use Mojolicious::Lite -signatures;
# Modify the received JSON document and return it
put '/reverse' => sub ($c) {
my $hash = $c->req->json;
$hash->{message} = reverse $hash->{message};
$c->render(json => $hash);
};
app->start;
You can send JSON documents from the command line with L<Mojolicious::Command::get>.
$ ./myapp.pl get -M PUT -c '{"message":"Hello Mojo!"}' /reverse
=head2 Built-in C<exception> and C<not_found> pages
During development you will encounter these pages whenever you make a mistake, they are gorgeous and contain a lot of
valuable information that will aid you in debugging your application.
use Mojolicious::Lite -signatures;
# Not found (404)
get '/missing' => sub ($c) {
$c->render(template => 'does_not_exist');
};
# Exception (500)
get '/dies' => sub { die 'Intentional error' };
app->start;
You can even use CSS selectors with L<Mojolicious::Command::get> to extract only the information you're actually
interested in.
$ ./myapp.pl get /dies '#error'
And don't worry about revealing too much information on these pages, they are only available during development, and
will be replaced automatically with pages that don't reveal any sensitive information in a production environment.
=head2 Route names
All routes can have a name associated with them, this allows automatic template detection and backreferencing with
L<Mojolicious::Controller/"url_for">, on which many methods and helpers like
L<Mojolicious::Plugin::TagHelpers/"link_to"> rely.
use Mojolicious::Lite -signatures;
# Render the template "index.html.ep"
get '/' => sub ($c) {
$c->render;
} => 'index';
# Render the template "hello.html.ep"
get '/hello';
app->start;
__DATA__
@@ index.html.ep
<%= link_to Hello => 'hello' %>.
<%= link_to Reload => 'index' %>.
@@ hello.html.ep
Hello World!
Nameless routes get an automatically generated one assigned that is simply equal to the route itself without non-word
characters.
=head2 Layouts
Templates can have layouts too, you just select one with the helper L<Mojolicious::Plugin::DefaultHelpers/"layout"> and
place the result of the current template with the helper L<Mojolicious::Plugin::DefaultHelpers/"content">.
use Mojolicious::Lite;
get '/with_layout';
app->start;
__DATA__
@@ with_layout.html.ep
% title 'Green';
% layout 'green';
Hello World!
@@ layouts/green.html.ep
<!DOCTYPE html>
<html>
<head><title><%= title %></title></head>
<body><%= content %></body>
</html>
The stash or helpers like L<Mojolicious::Plugin::DefaultHelpers/"title"> can be used to pass additional data to the
layout.
=head2 Blocks
Template blocks can be used like normal Perl functions and are always delimited by the C<begin> and C<end> keywords,
they are the foundation for many helpers.
use Mojolicious::Lite;
get '/with_block' => 'block';
app->start;
__DATA__
@@ block.html.ep
% my $link = begin
% my ($url, $name) = @_;
Try <%= link_to $url => begin %><%= $name %><% end %>.
% end
<!DOCTYPE html>
<html>
<head><title>Sebastians frameworks</title></head>
<body>
%= $link->('http://mojolicious.org', 'Mojolicious')
%= $link->('http://catalystframework.org', 'Catalyst')
</body>
</html>
=head2 Helpers
Helpers are little functions you can create with the keyword L<Mojolicious::Lite/"helper"> and reuse throughout your
whole application, from actions to templates.
use Mojolicious::Lite -signatures;
# A helper to identify visitors
helper whois => sub ($c) {
my $agent = $c->req->headers->user_agent || 'Anonymous';
my $ip = $c->tx->remote_address;
return "$agent ($ip)";
};
# Use helper in action and template
get '/secret' => sub ($c) {
my $user = $c->whois;
$c->app->log->debug("Request from $user");
};
app->start;
__DATA__
@@ secret.html.ep
We know who you are <%= whois %>.
A list of all built-in ones can be found in L<Mojolicious::Plugin::DefaultHelpers> and
L<Mojolicious::Plugin::TagHelpers>.
=head2 Plugins
Plugins are application extensions that help with code sharing and organization. You can load a plugin with the keyword
L<Mojolicious::Lite/"plugin">, which can omit the C<Mojolicious::Plugin::> part of the name, and optionally provide
configuration for the plugin.
use Mojolicious::Lite;
plugin Config => {file => '/etc/myapp.conf', default => {foo => 'bar'}};
# Return configured foo value, or default if no configuration file
get '/foo' => sub ($c) {
my $foo = $c->app->config('foo');
$c->render(json => {foo => $foo});
};
app->start;
L<Mojolicious::Plugin::Config> is a built-in plugin which can populate L<Mojolicious/"config"> using a config file.
Plugins can also set up routes, hooks, handlers, or even load other plugins. A list of built-in plugins can be found at
L<Mojolicious::Plugins/"PLUGINS">, and many more are available from
L<CPAN|https://metacpan.org/search?q=Mojolicious+Plugin>.
=head2 Placeholders
Route placeholders allow capturing parts of a request path until a C</> or C<.> separator occurs, similar to the
regular expression C<([^/.]+)>. Results are accessible via L<Mojolicious::Controller/"stash"> and
L<Mojolicious::Controller/"param">.
use Mojolicious::Lite -signatures;
# /foo/test
# /foo/test123
get '/foo/:bar' => sub ($c) {
my $bar = $c->stash('bar');
$c->render(text => "Our :bar placeholder matched $bar");
};
# /testsomething/foo
# /test123something/foo
get '/<:bar>something/foo' => sub ($c) {
my $bar = $c->param('bar');
$c->render(text => "Our :bar placeholder matched $bar");
};
app->start;
To separate them from the surrounding text, you can surround your placeholders with C<E<lt>> and C<E<gt>>, which also
makes the colon prefix optional.
=head2 Relaxed Placeholders
Relaxed placeholders allow matching of everything until a C</> occurs, similar to the regular expression C<([^/]+)>.
use Mojolicious::Lite;
# /hello/test
# /hello/test.html
get '/hello/#you' => 'groovy';
app->start;
__DATA__
@@ groovy.html.ep
Your name is <%= $you %>.
=head2 Wildcard placeholders
Wildcard placeholders allow matching absolutely everything, including C</> and C<.>, similar to the regular expression
C<(.+)>.
use Mojolicious::Lite;
# /hello/test
# /hello/test123
# /hello/test.123/test/123
get '/hello/*you' => 'groovy';
app->start;
__DATA__
@@ groovy.html.ep
Your name is <%= $you %>.
=head2 HTTP methods
Routes can be restricted to specific request methods with different keywords like L<Mojolicious::Lite/"get"> and
L<Mojolicious::Lite/"any">.
use Mojolicious::Lite -signatures;
# GET /hello
get '/hello' => sub ($c) {
$c->render(text => 'Hello World!');
};
# PUT /hello
put '/hello' => sub ($c) {
my $size = length $c->req->body;
$c->render(text => "You uploaded $size bytes to /hello.");
};
# GET|POST|PATCH /bye
any ['GET', 'POST', 'PATCH'] => '/bye' => sub ($c) {
$c->render(text => 'Bye World!');
};
# * /whatever
any '/whatever' => sub ($c) {
my $method = $c->req->method;
$c->render(text => "You called /whatever with $method.");
};
app->start;
=head2 Optional placeholders
All placeholders require a value, but by assigning them default values you can make capturing optional.
use Mojolicious::Lite -signatures;
# /hello
# /hello/Sara
get '/hello/:name' => {name => 'Sebastian', day => 'Monday'} => sub ($c) {
$c->render(template => 'groovy', format => 'txt');
};
app->start;
__DATA__
@@ groovy.txt.ep
My name is <%= $name %> and it is <%= $day %>.
Default values that don't belong to a placeholder simply get merged into the stash all the time.
=head2 Restrictive placeholders
A very easy way to make placeholders more restrictive are alternatives, you just make a list of possible values.
use Mojolicious::Lite -signatures;
# /test
# /123
any '/:foo' => [foo => ['test', '123']] => sub ($c) {
my $foo = $c->param('foo');
$c->render(text => "Our :foo placeholder matched $foo");
};
app->start;
All placeholders get compiled to a regular expression internally, this process can also be customized. Just make sure
not to use C<^> and C<$>, or capturing groups C<(...)>, non-capturing groups C<(?:...)> are fine though.
use Mojolicious::Lite -signatures;
# /1
# /123
any '/:bar' => [bar => qr/\d+/] => sub ($c) {
my $bar = $c->param('bar');
$c->render(text => "Our :bar placeholder matched $bar");
};
app->start;
You can take a closer look at all the generated regular expressions with the command L<Mojolicious::Command::routes>.
$ ./myapp.pl routes -v
=head2 Under
Authentication and code shared between multiple routes can be realized easily with routes generated by
L<Mojolicious::Lite/"under">. All following routes are only evaluated if the callback returned a true value.
use Mojolicious::Lite -signatures;
# Authenticate based on name parameter
under sub ($c) {
# Authenticated
my $name = $c->param('name') || '';
return 1 if $name eq 'Bender';
# Not authenticated
$c->render(template => 'denied');
return undef;
};
# Only reached when authenticated
get '/' => 'index';
app->start;
__DATA__
@@ denied.html.ep
You are not Bender, permission denied.
@@ index.html.ep
Hi Bender.
Prefixing multiple routes is another good use for it.
use Mojolicious::Lite;
# /foo
under '/foo';
# /foo/bar
get '/bar' => {text => 'foo bar'};
# /foo/baz
get '/baz' => {text => 'foo baz'};
# / (reset)
under '/' => {msg => 'whatever'};
# /bar
get '/bar' => {inline => '<%= $msg %> works'};
app->start;
You can also group related routes with L<Mojolicious::Lite/"group">, which allows nesting of routes generated with
L<Mojolicious::Lite/"under">.
use Mojolicious::Lite -signatures;
# Global logic shared by all routes
under sub ($c) {
return 1 if $c->req->headers->header('X-Bender');
$c->render(text => "You're not Bender.");
return undef;
};
# Admin section
group {
# Local logic shared only by routes in this group
under '/admin' => sub ($c) {
return 1 if $c->req->headers->header('X-Awesome');
$c->render(text => "You're not awesome enough.");
return undef;
};
# GET /admin/dashboard
get '/dashboard' => {text => 'Nothing to see here yet.'};
};
# GET /welcome
get '/welcome' => {text => 'Hi Bender.'};
app->start;
=head2 Formats
Formats can be automatically detected from file extensions like C<.html>, they are used to find the right template and
generate the correct C<Content-Type> header.
use Mojolicious::Lite -signatures;
# /detection
# /detection.html
# /detection.txt
get '/detection' => sub ($c) {
$c->render(template => 'detected');
};
app->start;
__DATA__
@@ detected.html.ep
<!DOCTYPE html>
<html>
<head><title>Detected</title></head>
<body>HTML was detected.</body>
</html>
@@ detected.txt.ep
TXT was detected.
The default format is C<html>, and restrictive placeholders can be used to limit possible values.
use Mojolicious::Lite -signatures;
# /hello.json
# /hello.txt
get '/hello' => [format => ['json', 'txt']] => sub ($c) {
return $c->render(json => {hello => 'world'})
if $c->stash('format') eq 'json';
$c->render(text => 'hello world');
};
app->start;
Or you can just disable format detection with a special type of restrictive placeholder.
use Mojolicious::Lite;
# /hello
get '/hello' => [format => 0] => {text => 'No format detection.'};
# Disable detection and allow the following routes to re-enable it on demand
under [format => 0];
# /foo
get '/foo' => {text => 'No format detection again.'};
# /bar.txt
get '/bar' => [format => 'txt'] => {text => ' Just one format.'};
app->start;
=head2 Content negotiation
For resources with different representations and that require truly RESTful content negotiation you can also use
L<Mojolicious::Plugin::DefaultHelpers/"respond_to">.
use Mojolicious::Lite -signatures;
# /hello (Accept: application/json)
# /hello (Accept: application/xml)
# /hello.json
# /hello.xml
# /hello?format=json
# /hello?format=xml
get '/hello' => sub ($c) {
$c->respond_to(
json => {json => {hello => 'world'}},
xml => {text => '<hello>world</hello>'},
any => {data => '', status => 204}
);
};
app->start;
MIME type mappings can be extended or changed easily with L<Mojolicious/"types">.
app->types->type(rdf => 'application/rdf+xml');
=head2 Static files
Similar to templates, but with only a single file extension and optional Base64 encoding, static files can be inlined
in the C<DATA> section and are served automatically.
use Mojolicious::Lite;
app->start;
__DATA__
@@ something.js
alert('hello!');
@@ test.txt (base64)
dGVzdCAxMjMKbGFsYWxh
External static files are not limited to a single file extension and will be served automatically from a C<public>
directory if it exists.
$ mkdir public
$ mv something.js public/something.js
$ mv mojolicious.tar.gz public/mojolicious.tar.gz
Both have a higher precedence than routes for C<GET> and C<HEAD> requests. Content negotiation with C<Range>,
C<If-None-Match> and C<If-Modified-Since> headers is supported as well and can be tested very easily with
L<Mojolicious::Command::get>.
$ ./myapp.pl get /something.js -v -H 'Range: bytes=2-4'
=head2 External templates
External templates will be searched by the renderer in a C<templates> directory if it exists.
$ mkdir -p templates/foo
$ echo 'Hello World!' > templates/foo/bar.html.ep
They have a higher precedence than templates in the C<DATA> section.
use Mojolicious::Lite -signatures;
# Render template "templates/foo/bar.html.ep"
any '/external' => sub ($c) {
$c->render(template => 'foo/bar');
};
app->start;
=head2 Home
You can use L<Mojolicious/"home"> to interact with the directory your application considers its home. This is the
directory it will search for C<public> and C<templates> directories, but you can use it to store all sorts of
application specific data.
$ mkdir cache
$ echo 'Hello World!' > cache/hello.txt
There are many useful methods L<Mojo::Home> inherits from L<Mojo::File>, like L<Mojo::File/"child"> and
L<Mojo::File/"slurp">, that will help you keep your application portable across many different operating systems.
use Mojolicious::Lite -signatures;
# Load message into memory
my $hello = app->home->child('cache', 'hello.txt')->slurp;
# Display message
get '/' => sub ($c) {
$c->render(text => $hello);
};
You can also introspect your application from the command line with L<Mojolicious::Command::eval>.
$ ./myapp.pl eval -v 'app->home'
=head2 Conditions
Conditions such as C<agent> and C<host> from L<Mojolicious::Plugin::HeaderCondition> allow even more powerful route
constructs.
use Mojolicious::Lite -signatures;
# Firefox
get '/foo' => (agent => qr/Firefox/) => sub ($c) {
$c->render(text => 'Congratulations, you are using a cool browser.');
};
# Internet Explorer
get '/foo' => (agent => qr/Internet Explorer/) => sub ($c) {
$c->render(text => 'Dude, you really need to upgrade to Firefox.');
};
# http://mojolicious.org/bar
get '/bar' => (host => 'mojolicious.org') => sub ($c) {
$c->render(text => 'Hello Mojolicious.');
};
app->start;
=head2 Sessions
Cookie-based sessions just work out of the box, as soon as you start using them through the helper
L<Mojolicious::Plugin::DefaultHelpers/"session">. Just be aware that all session data gets serialized with
L<Mojo::JSON> and stored client-side, with a cryptographic signature to prevent tampering.
use Mojolicious::Lite -signatures;
# Access session data in action and template
get '/counter' => sub ($c) {
$c->session->{counter}++;
};
app->start;
__DATA__
@@ counter.html.ep
Counter: <%= session 'counter' %>
Note that you should use custom L<Mojolicious/"secrets"> to make signed cookies really tamper resistant.
app->secrets(['My secret passphrase here']);
=head2 File uploads
All files uploaded via C<multipart/form-data> request are automatically available as L<Mojo::Upload> objects from
L<Mojolicious::Controller/"param">. And you don't have to worry about memory usage, because all files above 250KiB will
be automatically streamed into a temporary file. To build HTML forms more efficiently, you can also use tag helpers
like L<Mojolicious::Plugin::TagHelpers/"form_for">.
use Mojolicious::Lite -signatures;
# Upload form in DATA section
get '/' => 'form';
# Multipart upload handler
post '/upload' => sub ($c) {
# Check file size
return $c->render(text => 'File is too big.', status => 200) if $c->req->is_limit_exceeded;
# Process uploaded file
return $c->redirect_to('form') unless my $example = $c->param('example');
my $size = $example->size;
my $name = $example->filename;
$c->render(text => "Thanks for uploading $size byte file $name.");
};
app->start;
__DATA__
@@ form.html.ep
<!DOCTYPE html>
<html>
<head><title>Upload</title></head>
<body>
%= form_for upload => (enctype => 'multipart/form-data') => begin
%= file_field 'example'
%= submit_button 'Upload'
% end
</body>
</html>
To protect you from excessively large files there is also a limit of 16MiB by default, which you can tweak with the
attribute L<Mojolicious/"max_request_size">.
# Increase limit to 1GiB
app->max_request_size(1073741824);
=head2 User agent
With L<Mojo::UserAgent>, which is available through the helper L<Mojolicious::Plugin::DefaultHelpers/"ua">, there's a
full featured HTTP and WebSocket user agent built right in. Especially in combination with L<Mojo::JSON> and
L<Mojo::DOM> this can be a very powerful tool.
use Mojolicious::Lite -signatures;
# Blocking
get '/headers' => sub ($c) {
my $url = $c->param('url') || 'https://mojolicious.org';
my $dom = $c->ua->get($url)->result->dom;
$c->render(json => $dom->find('h1, h2, h3')->map('text')->to_array);
};
# Non-blocking
get '/title' => sub ($c) {
$c->ua->get('mojolicious.org' => sub ($ua, $tx) {
$c->render(data => $tx->result->dom->at('title')->text);
});
};
# Concurrent non-blocking
get '/titles' => sub ($c) {
my $mojo = $c->ua->get_p('https://mojolicious.org');
my $cpan = $c->ua->get_p('https://metacpan.org');
Mojo::Promise->all($mojo, $cpan)->then(sub ($mojo, $cpan) {
$c->render(json => {
mojo => $mojo->[0]->result->dom->at('title')->text,
cpan => $cpan->[0]->result->dom->at('title')->text
});
})->wait;
};
app->start;
For more information about the user agent see also L<Mojolicious::Guides::Cookbook/"USER AGENT">.
=head2 WebSockets
WebSocket applications have never been this simple before. Just receive messages by subscribing to events such as
L<Mojo::Transaction::WebSocket/"json"> with L<Mojolicious::Controller/"on"> and return them with
L<Mojolicious::Controller/"send">.
use Mojolicious::Lite -signatures;
websocket '/echo' => sub ($c) {
$c->on(json => sub ($c, $hash) {
$hash->{msg} = "echo: $hash->{msg}";
$c->send({json => $hash});
});
};
get '/' => 'index';
app->start;
__DATA__
@@ index.html.ep
<!DOCTYPE html>
<html>
<head>
<title>Echo</title>
<script>
var ws = new WebSocket('<%= url_for('echo')->to_abs %>');
ws.onmessage = function (event) {
document.body.innerHTML += JSON.parse(event.data).msg;
};
ws.onopen = function (event) {
ws.send(JSON.stringify({msg: 'I ♥ Mojolicious!'}));
};
</script>
</head>
</html>
For more information about real-time web features see also L<Mojolicious::Guides::Cookbook/"REAL-TIME WEB">.
=head2 Mode
You can use the L<Mojo::Log> object from L<Mojolicious/"log"> to portably collect debug messages and automatically
disable them later in a production setup by changing the L<Mojolicious> operating mode, which can also be retrieved
from the attribute L<Mojolicious/"mode">.
use Mojolicious::Lite -signatures;
# Prepare mode specific message during startup
my $msg = app->mode eq 'development' ? 'Development!' : 'Something else!';
get '/' => sub ($c) {
$c->app->log->debug('Rendering mode specific message');
$c->render(text => $msg);
};
app->log->debug('Starting application');
app->start;
The default operating mode will usually be C<development> and can be changed with command line options or the
C<MOJO_MODE> and C<PLACK_ENV> environment variables. A mode other than C<development> will raise the log level from
C<debug> to C<info>. All messages will be written to C<STDERR> by default.
$ ./myapp.pl daemon -m production
Mode changes also affect a few other aspects of the framework, such as the built-in C<exception> and C<not_found>
pages. Once you switch modes from C<development> to C<production>, no sensitive information will be revealed on those
pages anymore.
=head2 Testing
Testing your application is as easy as creating a C<t> directory and filling it with normal Perl tests like
C<t/basic.t>, which can be a lot of fun thanks to L<Test::Mojo>.
use Test::More;
use Mojo::File qw(curfile);
use Test::Mojo;
# Portably point to "../myapp.pl"
my $script = curfile->dirname->sibling('myapp.pl');
my $t = Test::Mojo->new($script);
$t->get_ok('/')->status_is(200)->content_like(qr/Funky/);
done_testing();
Just run your tests with L<prove>.
$ prove -l -v
$ prove -l -v t/basic.t
=head1 MORE
You can continue with L<Mojolicious::Guides> now or take a look at the L<Mojolicious
wiki|https://github.com/mojolicious/mojo/wiki>, which contains a lot more documentation and examples by many different
authors.
=head1 SUPPORT
If you have any questions the documentation might not yet answer, don't hesitate to ask in the
L<Forum|https://forum.mojolicious.org> or the official IRC channel C<#mojo> on C<chat.freenode.net>
(L<chat now!|https://webchat.freenode.net/#mojo>).
=cut