Initial Commit
This commit is contained in:
264
database/perl/vendor/lib/Mojolicious/Guides/Contributing.pod
vendored
Normal file
264
database/perl/vendor/lib/Mojolicious/Guides/Contributing.pod
vendored
Normal 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 it’s 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. It’s 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 we’re 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 doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t 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
|
||||
1850
database/perl/vendor/lib/Mojolicious/Guides/Cookbook.pod
vendored
Normal file
1850
database/perl/vendor/lib/Mojolicious/Guides/Cookbook.pod
vendored
Normal file
File diff suppressed because it is too large
Load Diff
250
database/perl/vendor/lib/Mojolicious/Guides/FAQ.pod
vendored
Normal file
250
database/perl/vendor/lib/Mojolicious/Guides/FAQ.pod
vendored
Normal 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
|
||||
771
database/perl/vendor/lib/Mojolicious/Guides/Growing.pod
vendored
Normal file
771
database/perl/vendor/lib/Mojolicious/Guides/Growing.pod
vendored
Normal 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
|
||||
1429
database/perl/vendor/lib/Mojolicious/Guides/Rendering.pod
vendored
Normal file
1429
database/perl/vendor/lib/Mojolicious/Guides/Rendering.pod
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1011
database/perl/vendor/lib/Mojolicious/Guides/Routing.pod
vendored
Normal file
1011
database/perl/vendor/lib/Mojolicious/Guides/Routing.pod
vendored
Normal file
File diff suppressed because it is too large
Load Diff
644
database/perl/vendor/lib/Mojolicious/Guides/Testing.pod
vendored
Normal file
644
database/perl/vendor/lib/Mojolicious/Guides/Testing.pod
vendored
Normal 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
|
||||
956
database/perl/vendor/lib/Mojolicious/Guides/Tutorial.pod
vendored
Normal file
956
database/perl/vendor/lib/Mojolicious/Guides/Tutorial.pod
vendored
Normal 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
|
||||
Reference in New Issue
Block a user