
Short answer: In a Dancer2 application, an RSS feed is one route that builds an XML document with the XML::RSS module and returns it with the RSS content type. Keep your posts as a list of hashes or database rows, format every date in the RSS style from a UTC timestamp, make the item identifiers the permanent post addresses, answer conditional requests with a 304 status when nothing has changed and test the live address with curl and a validator. Once the feed is stable, an auto-poster can share each new post on your social networks.
Perl and Dancer2 are still a practical choice for small web services, internal tools and long-running sites that have worked well for years. Dancer2 gives you a simple routing syntax and sensible defaults, and the Perl archive holds a mature module for writing feeds. The danger is in the details that every feed shares: dates that depend on the locale of the server, text that is encoded twice, links that are not absolute and an identifier that changes when somebody edits a title. This guide builds a complete route, explains each decision and ends with a test routine that you can repeat after every change.
Generate a project with the Dancer2 command line tool, which creates a module for your routes, a configuration file and a PSGI file that a server can run. Install the feed module from the archive in the same way as any other dependency.
cpanm Dancer2 XML::RSS Plack
dancer2 gen -a Feed
cd Feed
plackup -p 5000 bin/app.psgiRoutes live in the module under the lib folder. In this guide the data is a small list in memory, which keeps the code short. In a real application the list would come from a database, and the rest of the code would stay the same. If you maintain other Perl services, compare this approach with the Mojolicious feed guide.
A feed item needs a title, a permanent address, a publication date and a summary. Store the date as a Unix timestamp, which is always in UTC, so that the time zone of the server cannot change what the feed says.
package Feed;
use Dancer2;
use XML::RSS;
use POSIX qw(strftime setlocale LC_TIME);
our $VERSION = '0.1';
my $site = 'https://example.com';
my @posts = (
{
slug => 'spring-opening-hours',
title => 'Spring Opening Hours',
summary => 'Our new opening hours start on Monday.',
epoch => 1793260800,
},
);
setlocale(LC_TIME, 'C');
sub rfc822 {
my ($epoch) = @_;
return strftime('%a, %d %b %Y %H:%M:%S +0000', gmtime($epoch));
}The locale line matters more than it looks. The day and month names that the formatting function prints depend on the locale of the process, and a server set to another language would produce a date such as a localised weekday that validators reject. Forcing the plain locale for time formatting makes the output the same on every machine.
The route sorts the posts, keeps the newest twenty, creates the channel, adds one item for every post and returns the document. XML::RSS escapes special characters in titles and descriptions, so you do not have to.
get '/feed.xml' => sub {
my @items = sort { $b->{epoch} <=> $a->{epoch} } @posts;
splice @items, 20 if @items > 20;
my $newest = $items[0]{epoch};
my $etag = sprintf('"%d-%d"', scalar(@posts), $newest);
response_header 'ETag' => $etag;
response_header 'Cache-Control' => 'public, max-age=300';
my $inm = request->header('If-None-Match') // '';
if ($inm eq $etag) {
status 304;
return '';
}
my $rss = XML::RSS->new(version => '2.0', encode_output => 0);
$rss->channel(
title => 'Example Journal',
link => $site,
description => 'News and guides from Example Journal.',
language => 'en',
pubDate => rfc822($newest),
lastBuildDate => rfc822($newest),
);
for my $p (@items) {
my $url = "$site/blog/$p->{slug}/";
$rss->add_item(
title => $p->{title},
link => $url,
permaLink => $url,
description => $p->{summary},
pubDate => rfc822($p->{epoch}),
);
}
content_type 'application/rss+xml';
return $rss->as_string;
};
true;Several decisions here deserve a closer look. The channel date comes from the newest post and not from the clock, so the file does not change when nothing was published; a feed that changes on every request defeats caching, as explained in the guide to ETags and conditional requests. The identifier of each item is its permanent address, which does not change when the title does. And the validator is built from the number of posts and the date of the newest one, so it changes exactly when the content changes.
The module can encode its own output, and Dancer2 can encode the response body according to the charset in the configuration. If both do, accented letters and emoji are encoded twice and turn into garbage. The route above asks the module to return characters and leaves the encoding to the framework, so set the charset once in the configuration file.
# config.yml
charset: "UTF-8"Check the option names against the documentation of the module version you installed. Also remove characters that are not allowed in XML at all, such as most control characters, if your text comes from users; they can break the file even though the module escapes everything else. More about the symptoms is in why special characters and emoji break feeds.
| Feed element | Source in the code | Rule |
|---|---|---|
| Title | title key of the post | Plain text, escaped by the module |
| Link and identifier | site address and slug | Absolute, https, never changes |
| Publication date | epoch key, formatted | In the past, RFC 822 style, offset 0000 |
| Description | summary key | One or two complete sentences |
| Channel date | Newest epoch | Changes only when a post is added |
| Cache validator | Count and newest epoch | Changes only when content changes |
The identifier deserves the most attention. If the slug follows the title and somebody corrects a typo, the address changes, and an auto-poster that stores identifiers will treat the old and new address as two posts. The causes and cures are covered in why a feed auto-posts the same article twice.
Auto-posters make the best social posts from a feed that gives them a short summary and a picture. Write a real description for every post instead of cutting the text automatically, because an automatic cut can end in the middle of a sentence and that is what the auto-poster will copy. If a post has an image, add an enclosure with its absolute address, its size in bytes and its media type. Relative paths inside a feed are broken for everyone who reads it away from your site; see why relative URLs break feed links.
Filter the list before building the feed. Drafts and anything with a date in the future should stay out, because an article that is shared before it exists on your website sends readers to a page that does not load. Whether to include the full text or only a summary is discussed in full text or excerpt in an RSS feed.
Add a link element to the head of your page layout so that browsers, readers and tools can find the feed from any page. Set the relation to alternate, the type to the RSS media type and the href to the absolute address of the feed. With that in place, a person or a tool that only knows your home page can locate the feed by itself. The full explanation is in the guide to feed autodiscovery.
The development server is fine for a first test, but a public feed should run under a production PSGI server behind a reverse proxy that terminates the encrypted connection. Three checks matter after the first deployment. The proxy must pass the request headers that the cache logic reads. It must not keep an old copy of the feed for longer than you intend, because a lagging feed delays every social post. And the address that outsiders use must be the same one that the code writes into the links, since the application cannot guess it from an internal request. If compression is enabled, confirm that the compressed response keeps the right content type; see the guide to feed compression.
Request the route locally, then test the deployed address from outside, because a local test says nothing about the proxy and the certificate in front of your application.
curl -i http://localhost:5000/feed.xml
curl -i https://example.com/feed.xml
curl -i -H 'If-None-Match: "1-1793260800"' https://example.com/feed.xmlA longer routine is in the feed validation guide, and a shorter one in debugging a feed with curl.
PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your feed, connect your accounts and choose how each post is built from the title, the description or the page title. PostRSS checks the feed as often as every minute and shares each new entry, with its image and link, to the channels you choose among 66 supported networks, messengers, team chats and blogs, including LinkedIn, X, Bluesky, Mastodon and Telegram. Keyword filters, UTM parameters and posting windows let you tune each channel. See the PostRSS features page and the plan limits on the pricing page. More developer guides are under the Technical Guide tag.
A Dancer2 feed is one route, but it has to get the details right: dates formatted from UTC timestamps in a fixed locale, text that is encoded exactly once, absolute links, identifiers that never change, a channel date taken from the newest post and a validator that lets clients skip unchanged files. Keep drafts and future posts out, test the live address with curl and a validator, then connect it to an auto-poster. After that, every post you publish reaches your readers and your social channels without extra work.
XML::RSS is the long-established choice for writing RSS from Perl. It escapes special characters and produces a valid document, and it can also read feeds if you ever need to.
Call the content type keyword with the RSS media type before you return the body. If a charset is set in the configuration, the framework adds it to the header.
The month or weekday names probably follow the locale of the server, or the offset is missing. Set the time locale to the plain one and format the date from a UTC timestamp with a fixed offset.
Set an ETag header built from a value that changes only when the content changes, compare it with the If-None-Match request header and return a 304 status with an empty body when they match.
Yes. PostRSS reads any valid RSS or Atom address that is reachable without a login. Paste the address, connect your channels and new posts are shared as soon as they appear.
What changed in the networks, what broke, and how to fix it before it costs you reach.
PostRSSを手がけるInternet Solutionsが開発。どの製品も、それぞれの形で時間を節約します。