A Punk blog with feeds
The example application for Punk::Feed.
Generated with punk new Blog and then given two feeds.
plackup app.psgi # or: hyperman app.psgi
prove -l t/
Then:
| URL | | |---|---| | http://localhost:5000/ | the posts, with autodiscovery tags in the head | | http://localhost:5000/feed.xml | Atom 1.0 | | http://localhost:5000/feed.rss | RSS 2.0 | | http://localhost:5000/feed/releases.xml | a second, named feed |
app.psgi and t/01-basic.t add the distribution's blib to @INC so the
demo runs before Punk::Feed is installed. Drop those lines once it is.
What to look at
The sections are controller methods. feed 'Web::Feed#posts'; in
lib/Blog.pm takes a 'Controller#method' target exactly as get does, and
the section itself lives in lib/Blog/Controller/Web/Feed.pm. A section that
reads rows is controller work. A closure works too, and is the right shape for
something too small to earn a method.
A section takes no context. It runs at to_app and again when the TTL has
passed, not per request, so there is no $c to hand it.
The origin is configuration. config/punk.yml declares
host: https://blog.example.com, and every URL in both documents is built on
it. The request's Host header is never consulted: it would let
Host: evil.example produce a feed naming that host for every item, and every
reader that fetched it would keep it for as long as they stayed subscribed.
That is why the URLs say blog.example.com even though you are reading this on
localhost.
Autodiscovery comes from $c->feed_links. The <link rel="alternate">
tags in the page head are generated, not typed into the layout - which is where
they go stale the first time path changes. The controller passes the value
through to the template; see _page in Web/Root.pm.
Escaping. The second post's title contains an ampersand. It reaches both
documents as &, because one bare & makes a feed not well-formed and a
reader rejects the whole thing rather than the offending entry.
Two dates, one format that can say so. That same post was edited after
publication, so Atom carries <updated> and <published> separately. RSS has
one date element and shows only the publication date - one of the things that
format cannot express.
Conditional GET. A reader asks for this URL every few minutes for years, so
every response carries an ETag and a Last-Modified:
curl -sD- -o/dev/null http://localhost:5000/feed.xml
curl -sD- -o/dev/null -H 'If-None-Match: "<the etag>"' http://localhost:5000/feed.xml
The second is a 304 with no body.
The feed timestamp is the newest entry's date, never now. A rebuild that
found nothing new produces the same bytes as the one before it - otherwise
every reader would record a change on every TTL. RSS spells that element
<lastBuildDate>, and the name is the trap.