Announcing Mojolicious 9.0
The Mojolicious Core Team is delighted to announce the release and immediate availability of version 9.0 of the Mojolicious real-time web framework. Every Mojolicious major release has a code name based on a unicode character and this one is lovingly named "Waffle" 🧇!
Every major release I think that there can't be much more to add or change in Mojolicious and then when the next one arrives I reflect on how much has changed since I last thought that. In 9.0 there's far too much to discuss each at length, so I'm going to highlight some of my favorites and then include a distilled list of changes since 8.0.
Perl 5.16 and beyond
Mojolicious now requires Perl version 5.16. This change gives us useful tools to build Mojolicious including the __SUB__
token for building self-referential callbacks without leaking.
We were pleased that the community response since the change a few months ago has been ... well crickets.
This is great news since we do hope to move to Perl 5.20 in the not so distant future so that we can use Perl's native signatures once that is a sound option for us.
We are already encouraging the use of signatures both in the documentation and in our code generation commands.
Asynchronous Functionality
Clearly the highlight of the pre-9.0 development cycle has been the integration of Async/Await.
Thanks to Paul Evans (LeoNerd)'s efforts, when you have Future::AsyncAwait installed Mojolicious can use its new keywords async
and await
to provide the most seamless asynchronous development possible, something that is becoming more and more the standard for asynchronous code in other languages.
Writing a non-blocking endpoint is now as simple as
use Mojolicious::Lite -signatures, -async_await;
# Request HTML titles from two sites non-blocking
get '/' => async sub ($c) {
my $mojo_tx = await $c->ua->get_p('https://mojolicious.org');
my $mojo_title = $mojo_tx->result->dom->at('title')->text;
my $cpan_tx = await $c->ua->get_p('https://metacpan.org');
my $cpan_title = $cpan_tx->result->dom->at('title')->text;
$c->render(json => {mojo => $mojo_title, cpan => $cpan_title});
};
app->start;
When I teach non-blocking to people I can now tell them to follow a trivial recipe for most non-blocking tasks.
Simply await
any function that returns a promise and mark any function that uses await
with the async
keyword.
Note also that all async
functions return promises so await
any calls to them.
There are some optimizations you can make at times and top-level await
(if you aren't in a Mojolicious webapp) can be a little strange but to a first approximation that's all you need to write a non-blocking webapp today!
Speaking of promises (which are at the heart of Async/Await), Mojo::Promise has grown a lot this cycle too, adding all_settled
, any
, map
, timer
, and timeout
class methods, changing its constructor to be more like the one in JavaScript and to warn when an unhandled rejected promise is destroyed.
The latter can help in development where such cases used to hide errors, now, much like the browser, you can at least find where things are going wrong even if you didn't wire all your promises to a catch callback like you should.
Focus on Containerization and Cloud Deployments
The Mojolicious Core Team is aware that more and more deployments are in containers and cloud services these days and we've been thinking of ways to make that easier. With 9.0 we've added two new features specifically designed to help with these types of deployments.
Most existing applications assume a standard deployment by an unprivileged user behind a reverse proxy, but with new deployment strategies the number of configurations an application might expect are too numerous to expect each application to support. To address this we've added support for deployment specific plugins that help you add deployment-level functionality to existing applications. Now the deployer is in control of these features, rather than needing to ask the application author to add support.
We've also extended the existing reverse proxy support to allow for multiple trusted proxies, specifically when you know what the expected IP address or CIDR network you're expecting proxy requests from. This can be useful both when deploying from say Kubernetes where it is likely you'll have several layers of local proxies, or when you're behind a third-party proxy like CloudFlare, who publish a list of their proxy networks. This is an important safety feature in these situations, blindly trusting forwarding headers can lead to big problems.
Also, we've noticed that many cloud deployment tools are based on YAML, so we've added a YAML configuration file loader to our existing Perl and JSON configuration loaders. Because YAML is a tricky target and because ours is technically not-quite-YAML, we've called ours cheekily NotYAMLConfig. In the unlikely event that that distinction should matter for your needs, you can load a specific YAML module from CPAN.
But it isn't just functionality, we've added a Dockerfile generation (myapp generate dockerfile
) and added a Container Cookbook recipe to the documentation.
We've also added a Cookbook recipe for the popular cloud-native proxy Envoy.
If you have other common container or cloud recipes that you think should be added, please let us know!
Documentation Improvements
Speaking of documentation, you might have noticed that both our documentation site and built-in templates for 404 and 500 pages have gotten a face lift.
The Mojolicious 8.69 release brings redesigned exception and not_found pages with a fresh new look, enjoy. pic.twitter.com/MHzvYnBJJw
— Mojolicious (@perlmojo) December 29, 2020
They're even prettier and easier to read than before, they're responsive, and they have a newer code library (highlight.js) to better render our code.
Further the documentation site is now redeployed immediately on any push to master of the website or the main Mojolicious projects, mojolicious
, mojo-pg
, and minion
, so that you always have the most up-to-date docs at your fingertips.
Along with the container and cloud-native changes I've already highlighted, we have updated our documentation to use of upcoming Perl native signatures throughout and paired it with updating our app
and lite-app
generators to generate applications with signatures too.
Routing (and Rendering)
The routing engine is an often overlooked part of a web framework but it really is at the heart of everything that it does. It can also be a source of confusion or frustration, especially when it doesn't do what you expect. For 9.0 we've made the router both safer and more closely aligned to user expectations.
The biggest change is that Mojolicious now prevents applications from using reserved placeholders in routes. This was originally considered a "feature" because it allowed very terse route definitions for quick apps, but in practice most people that understood how that worked just used the long form while unsuspecting newcomers would trip on it from time to time. We've also made several cases throw exceptions when routing would fail that used to fail silently or unexpectedly. When a route points to a missing controller, a namespace without a controller or a controller without an action, you now get an exception rather than a 404, which would often confuse people. Additionally, you get an exception when when auto-rendering fails or a call to render cannot render a response, which would appear to hang (while it actually was trying to wait for a delayed response that was never going to happen).
We've even simplified some of the routing method names themselves.
We've removed the lesser-used detour
and route
methods in favor of the more generic any
method.
We've renamed method that designates which HTTP methods to respond to (usually when picking several but not all of them, e.g. GET
and POST
but not any
) from via
to methods
.
We've also renamed the method that designates conditional routing from over
to requires
.
I'm especially excited about this one since over
implied a contrast with the commonly used under
method, where no such contrast is meant to be implied; they do very different things.
Logging
The big one for me is that Mojolicious now is capable of sanely using a contextual logger!
Mojolicious applications have a log
attribute that holds a Mojo::Log
instance, however that logger is global to the application and any context you'd want to add is only for a given request/response cycle.
Unlike blocking frameworks, a Mojolicious app might switch between several requests before any are rendered, meaning manually keeping context attached to that global logger was nearly impossible.
With 9.0 we now have the ability to use a per-controller "child" logger which descends from the global logger and still logs via it, but can keep its own context information.
It feeds that information to its parent when you log from it.
By default, these child loggers now have a random request id attached to it, however you can put whatever context you'd like to add there.
You can also pass these loggers (themselves an instance of Mojo::Log
) to models or other code that might want to log with that context attached.
We've also improved logging performance in cases where generating the log message is expensive and useless if the log level means that the log message is not emitted.
If you pass a closure (code reference) to the logger's debug
, info
, warn
, error
, and fatal
methods and the logger will only invoke that closure if the log level check is met.
In fact this improved Mojolicious' own performance metrics by 10%!
$c->log->debug(sub{ 'Thought you might want to know: ' . generate_expensive_log_output() });
Please note that we have changed the log format slightly so be aware if you care about such things.
And to simplify things, we've removed the confusing behavior of logging to a file if a log
directory exists.
While it sometimes did what people wanted, it more often than not confused newcomers who couldn't find their logs.
The behavior now is to always log to STDERR
but you can easily change it to point the file of your choice while your application is starting up.
Proxy Helpers
How often have you written an application which takes a request from a client, makes a request of another server, then responds back to the client with the result? I've done that quite often. While it may seem trivial to do, there can be some subtle problems like what do you do if the read and write speeds mismatch badly enough? Never thought about that? Well you don't have to anymore.
Mojolicious now comes with proxy helpers, the generic proxy->start_p
and the shortcuts proxy->get_p
and proxy->post_p
for common GET and POST requests.
Proxying a GET request in an action can now be as simple as
$c->proxy->get_p('http://mojolicious.org')->catch(sub ($err) {
$c->log->debug("Proxy error: $err");
$c->render(text => 'Something went wrong!', status => 400);
});
while using start_p
you can customize the transactions to your exact needs by subscribing to available events.
See the linked documentation for an example.
Mojo::DOM
CSS Selectors are amazing, but one major failing is that you can only extract the last element in your selector, not something in the middle.
Or to put it another way, they lack is the ability to conditionally match an element based on a property of its children.
Well CSS4 proposes something that lets you assert just such a condition, the :has
pseudo-class.
Without it, if you wanted to find all the links that have images as their content, you'd have to match a > img
and traverse back up to the parent (which gets even harder if it isn't a direct child, e.g. a img
).
However now you can simply do something like
$ mojo get https://mojolicious.org 'a:has(> img)' attr href
for only direct descendants or
$ mojo get https://mojolicious.org 'a:has(img)' attr href
to see all such links.
In its syntax, the inner selector is a "relative selector" where the a
element in question, called the :scope
, is implicitly a "virtual root".
$ mojo get https://mojolicious.org 'a:has(:scope > img)' attr href
would be the same query.
Another neat feature we've imported from CSS4 is the :is
pseudo-class.
In CSS3 you can "or" together several queries with a comma, but only for each query as a whole.
If you wanted to search for h1
elements inside of other tags, you'd have to do for example section h1, article h1, aside h1, nav h1
.
CSS4 gives you the :is
pseudo-class that lets you group logical portions of a selector together so you can make a more concise and comprehensible query.
The previous example can be rewritten as :is(section, article, aside, nav) h1
.
Now you've seen that I've mentioned that these are CSS4 features but CSS4 is not yet had a stable release, it is still being changed and updated. Mojolicious supports these selectors (and a few others from CSS4) but they will continue to be marked as experimental until the spec is released officially. Until then they might be changed or removed as the CSS4 spec changes.
Team and Process
During the 9.0 development cycle the team grew! We've welcomed CandyAngel and Christopher Rasch-Olsen Raa (mishanti1) and welcomed back Dan Book (Grinnz). We're very happy to have them!
We've also added some automation. In addition to the documentation site that I've already mentioned, and existing test runners, we've also added a perltidy check so that all PRs conform to our rules about it. We also simplified our PR process so that if a PR gets two approvals from the team it is automatically merged. This is a wonderful process boost since it encourages PRs to be reviewed and ensures that PRs with interest can see action quickly.
Finally, We're always looking for more help and to encourage people to do so, we've decided to open the process somewhat. We are encouraging people to review PRs even if you don't have review rights. This will do two things for us, first it will give us more opinions both in regards to community interest and from people who might have technical knowledge of the topic. Secondly, we've started tracking who is reviewing PRs and if you catch our notice, you might be on track to join more officially.
If this sounds interesting, at any level, we hope to see you on our pulls page. While you are there, you might see that we've enabled github discussions as an alternative to (and possible eventual replacement for) our mailing list.
Thanks for reading this! We hope you'll love Mojolicious 9.0!
As promised here is a distillation of all the non-trivial Changes since 8.0.
- Increased Perl version requirement to 5.16.0. This is just a first step however, at some point in the not so distant future we will increase the Perl version requirement to 5.20.0 for full subroutine signatures support
- Async/Await
- Requires
Future::AsyncAwait
0.36 - enable with
-async_await
- Requires
Mojo::Promise
enhancements- Added
all_settled
,any
,map
,timer
,timeout
- constructor arguments more like Javascript
- warn when unhandled rejected promise is destroyed
- Added
MOJO_PROMISE_DEBUG
environment variable - Improved
wait
method in Mojo::Promise not to be affected by manually stopped event loops - Improved
eval
command with support for promises
- Added
- Docker
generate dockerfile
command- Container cookbook recipe
- Envoy deployment recipe
- Trusted proxy support
- Added support for deployment specific plugins
- Disallowed the use of reserved stash values, such as
/:action
, in route patterns - Make routing safer and clearer
- Throw exceptions for missing controllers
- Disallowed namespace without controller for routing
- Throw exceptions for routes with controllers but without action
- Die if auto rendering failed or call to
$c->render
cannot render a response
- Cleaned up lesser-used router methods
- Removed
detour
,route
(useany
) - Renamed
over
torequires
,via
tomethods
- Removed
- Contextual logger
- Added
log
helper to which builds a child logger with context (request id) - Added
context
method toMojo::Log
- Added
- Slightly changed log format (datetime format, process id, lines joined with spaces)
- Removed automatically logging to
log/$mode.log
if log directory exists, default is alwaysSTDERR
now - Improved log messages generated by Mojolicious to include request ids when possible
- Improved
debug
,error
,fatal
,info
andwarn
methods in Mojo::Log to accept closures to generate log messages on demand, so expensive code for debugging can be deactivated easily in production- Improved Mojolicious performance by up to 10% with more efficient logging
- Added support for YAML config files:
- Added module
Mojolicious::Plugin::NotYAMLConfig
- Improved app generator command to use a YAML config file
- Added module
- Proxying with backpressure monitoring
- Added
proxy->get_p
,proxy->post_p
andproxy->start_p
helpers - Added
high_water_mark
attribute in Mojo::IOLoop::Stream - Added
bytes_waiting
andcan_write
methods in Mojo::IOLoop::Stream
- Added
- Added EXPERIMENTAL support for SameSite cookies to better protect Mojolicious applications from CSRF attacks
- Added EXPERIMENTAL
samesite
attributes toMojo::Cookie::Response
andMojolicious::Cookies
- Added EXPERIMENTAL
- Improved helper performance (
Mojo::DynamicMethods
) - Added
before_command
hook (expimental) Mojo::DOM
all_text
method now excludes<script>
and<style>
from text extraction in HTML documents
Mojo::DOM::CSS
- Added
:scope
,:has
(experimental) - Added
:is
(experimental) - Added
:any-link
(experimental) - Case-sensitive attribute selectors like
[foo="bar" s]
- Added
Mojo::IOLoop::Subprocess
- Added support for progress updates (
progress
method and event) - Added
run_p
method - Added
exit_code
method - Added
cleanup
event
- Added support for progress updates (
- Improved
Mojolicious::Commands
to treat commands likemojo generate lite_app
asmojo generate lite-app
Test::Mojo
- Improved extenability and testability of
Test::Mojo
withtest
method andhandler
attribute - Added
attr_is
,attr_isnt
,attr_like
andattr_unlike
methods - Added
header_exists
andheader_exists_not
methods
- Improved extenability and testability of
Mojolicious::Validator
- Added
not_empty
filter - Simplify
size
check - Fixed validator to also validate empty string values instead of ignoring them. This behaviour had caused a lot of confusion in the past
- Added
- Added compression (gzip) functionality to
Mojolicious::Renderer
for dynamic content- activate with
Mojolicious::Renderer::compress
(see the Rendering Guide)
- activate with
- Improved
is_fresh
method in Mojolicious::Static with support for weak etags Mojo::Exception
- Added support for
MOJO_EXCEPTION_VERBOSE
environment variable raise
,check
functions- include a stack trace in verbose output
- Added support for
Mojo::Base
- Improved
Mojo::Base
flags not to require a certain order - Added support for weak reference accessors to
Mojo::Base
- Improved
Mojo::Base
to enable the Perl 5.16 feature bundle withunicode_strings
,unicode_eval
,evalbytes
,current_sub
andfc
- Improved
Mojo::Server::Daemon
(and thus the other servers) now differentiate betweeninactivity_timeout
andkeep_alive_timeout
Mojo::Reactor::again
can now change the timeout for a retry- New conveniences
- Added
Mojo::Util::network_contains
- Added
Mojo::Util::scope_guard
- Added
content_type
andfile_types
toMojolicious::Types
- Added
extname
,curfile
,stat
,lstat
,remove
, andtouch
toMojo::File
humanize_bytes
inMojo::Util
andMojo::ByteStream
- Added
head
andtail
toMojo::Collection
- Added
l
function to ojo - Added
save_to
method to Mojo::Message
- Added
- Website improvements
- Style
- Responsiveness
- Replaced
prettify.js
withhighlight.js
- auto-deployed on push to master
- Built-in templates updated too
- Encouraged signatures
- Improved
app
andlite_app
generators to use templates with subroutine signatures - Updated all documentation to use subroutine signatures
- Improved
- Removed module Mojo::IOLoop::Delay
- Spun out to CPAN for long term compat (but not continued development)
- Removed deprecated success method from
Mojo::Transaction
(use!$tx->error
or$tx->result
) - Cleaned up lesser used connection properties in
Mojo::UserAgent
andMojo::IOLoop::Client
- Removed
config
stash value - Removed
Mojo::Collection::slice
Joel Berger
Joel has Ph.D. in Physics from the University of Illinois at Chicago. He an avid Perl user and author and is a member of the Mojolicious Core Team.