implementation("io.micronaut.rss:micronaut-rss")
Micronaut RSS
Eases the creation of RSS feeds with Micronaut
Version: 4.4.0
1 Introduction
Micronaut RSS eases the creation of RSS feeds with Micronaut.
2 Release History
For this project, you can find a list of releases (with release notes) here:
3 What's New?
Micronaut RSS 2.0 uses Micronaut 2.0.
A new module, rss-core
, contains core RSS related file. rss
module depends on rss-core
and HTTP related classes such as FeedController.
4 Breaking Changes
4.1 Breaking Changes in Micronaut RSS 3.0.0
-
Classes, constructors, etc that have been deprecated in previous versions of Micronaut RSS have been removed.
-
RssFeedProvider returns a
Publisher
4.2 Breaking Changes in Micronaut RSS 2.0.0
FeedController, FeedControllerConfiguration and FeedControllerConfigurationProperties moved to package io.micronaut.rss.http
.
:micronaut-itunespodcast
depends only on micronaut-rss-core
. You may want to use it in combination with micronaut-rss
if you want to use the FeedController.
The artifacts' maven group has been changed to micronaut-rss
. The next table shows the maven coordinates changes:
Old |
New |
|
|
|
|
|
|
5 RSS 2.0
<dependency>
<groupId>io.micronaut.rss</groupId>
<artifactId>micronaut-rss</artifactId>
</dependency>
This modules exposes a controller FeedController which exposes feeds as described by RSS 2.0 Specification.
The following table displays several characteristics which can be configured:
Property | Type | Description |
---|---|---|
|
boolean |
Whether FeedController should be enabled. Default value (true). |
|
java.lang.String |
FeedController
requires two beans to be present RssFeedProvider and RssFeedRenderer. While DefaultRssFeedRenderer provides a default implementation for RssFeedRenderer
, you will need to provide an implementation for RssFeedProvider
.
For example, if you wanted to generate a feed as the one described in the RSS 2.0 specifiation’s sample you could create the next bean:
@Singleton
class MockRssFeedProvider implements RssFeedProvider {
RssChannel rssChannel = RssChannel.builder("Liftoff News", "http://liftoff.msfc.nasa.gov/", "Liftoff to Space Exploration.")
.language(RssLanguage.LANG_ENGLISH_UNITED_STATES)
.pubDate(ZonedDateTime.of(LocalDateTime.of(2003, 6, 10, 4, 0, 0), ZoneId.of("GMT")))
.lastBuildDate(ZonedDateTime.of(LocalDateTime.of(2003, 6, 10, 9, 41, 1), ZoneId.of("GMT")))
.docs("http://blogs.law.harvard.edu/tech/rss")
.generator("Weblog Editor 2.0")
.managingEditor("editor@example.com")
.webMaster("webmaster@example.com")
.item(RssItem.builder()
.title("Star City")
.link("http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp")
.description("How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's <a href=\"http://howe.iki.rssi.ru/GCTC/gctc_e.htm\">Star City</a>.")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2003, 6, 3, 9, 39, 21), ZoneId.of("GMT")))
.guid("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573")
.build())
.item(RssItem.builder()
.description("Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a <a href=\"http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm\">partial eclipse of the Sun</a> on Saturday, May 31st.")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2003, 5, 30, 11, 6, 42), ZoneId.of("GMT")))
.guid("http://liftoff.msfc.nasa.gov/2003/05/30.html#item572")
.build())
.item(RssItem.builder()
.title("The Engine That Does More")
.link("http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp")
.description("Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that.")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2003, 5, 27, 8, 37, 32), ZoneId.of("GMT")))
.guid("http://liftoff.msfc.nasa.gov/2003/05/27.html#item571")
.build())
.item(RssItem.builder()
.title("Astronauts' Dirty Laundry")
.link("http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp")
.description("Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options.")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2003, 5, 20, 8, 56, 02), ZoneId.of("GMT")))
.guid("http://liftoff.msfc.nasa.gov/2003/05/20.html#item570")
.build())
.build();
@Override
@SingleResult
public Publisher<RssChannel> fetch() {
return Publishers.just(rssChannel);
}
@Override
@SingleResult
public Publisher<RssChannel> fetchById(Serializable id) {
if(id != null && id.equals("1")) {
return Publishers.just(rssChannel);
}
return Publishers.empty();
}
}
6 Itunes Podcast RSS Feed
implementation("io.micronaut.rss:micronaut-itunespodcast")
<dependency>
<groupId>io.micronaut.rss</groupId>
<artifactId>micronaut-itunespodcast</artifactId>
</dependency>
An Itunes podcast feed, is a valid RSS 2.0 feed with additional tags.
For example, the following bean will generate the Itunes Podcast RSS Feed’s sample
@Singleton
public class DefaultRssFeedProvider implements RssFeedProvider {
@Override
@SingleResult
public Publisher<RssChannel> fetch() {
return Publishers.just(ItunesPodcast.builder()
.title("Hiking Treks")
.link("https://www.apple.com/itunes/podcasts/")
.language(RssLanguage.LANG_ENGLISH_UNITED_STATES)
.copyright("℗ & © 2017 John Appleseed")
.description("Love to get outdoors and discover nature's treasures? Hiking Treks is the show for you. We review hikes and excursions, review outdoor gear and interview a variety of naturalists and adventurers. Look for new episodes each week.")
.subtitle("Find your trail. Great hikes and outdoor adventures.")
.author("The Sunset Explorers")
.subtitle("Find your trail. Great hikes and outdoor adventures.")
.type(ItunesPodcastType.SERIAL)
.owner(ItunesPodcastOwner.builder()
.name("Sunset Explorers")
.email("mountainscape@icloud.com")
.build())
.image(RssChannelImage
.builder("Hiking Treks", "http://podcasts.apple.com/resources/example/hiking_treks/images/cover_art.jpg", "https://www.apple.com/itunes/podcasts/")
.build())
.summary("Love to get outdoors and discover nature's treasures? Hiking Treks is the show for you. We review hikes and excursions, review outdoor gear and interview a variety of naturalists and adventurers. Look for new episodes each week.")
.category(Arrays.asList(ItunesPodcastCategory.SPORTS_AND_RECREATION_OUTDOOR.getCategories()))
.explicit(false)
.item(ItunesPodcastEpisode.builder("Hiking Treks Trailer")
.episodeType(ItunesPodcastEpisodeType.TRAILER)
.author("The Sunset Adventurers")
.subtitle("The Sunset Explorers share tips, techniques and recommendations for great hikes and adventures around the United States.")
.summary("The Sunset Explorers share tips, techniques and recommendations for great hikes and adventures around the United States.")
.description("The Sunset Explorers share tips, techniques and recommendations for great hikes and adventures around the United States.")
.contentEncoded("<![CDATA[The Sunset Explorers share tips, techniques and recommendations for great hikes and adventures around the United States. Listen on <a href=\"https://www.apple.com/itunes/podcasts/\">Apple Podcasts</a>]]>")
.enclosure(RssItemEnclosure.builder()
.length(498537)
.type("audio/mpeg")
.url("http://example.com/podcasts/everything/AllAboutEverythingEpisode4.mp3")
.build())
.guid("http://example.com/podcasts/archive/aae20160418.mp3")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2016, 4, 12, 1, 15, 0), ZoneId.of("GMT")))
.duration("17:59")
.explicit(false)
.build())
.item(ItunesPodcastEpisode.builder("Mt. Hood, Oregon")
.episodeType(ItunesPodcastEpisodeType.FULL)
.episode(4)
.season(2)
.title("S02 EP04 Mt. Hood, Oregon")
.author("The Sunset Explorers")
.subtitle("Tips for trekking around the tallest mountain in Oregon")
.summary("Tips for trekking around the tallest mountain in Oregon")
.description("Tips for trekking around the tallest mountain in Oregon")
.contentEncoded("Tips for trekking around the tallest mountain in Oregon")
.enclosure(RssItemEnclosure.builder()
.length(8727310)
.type("audio/x-m4a")
.url("http://example.com/podcasts/everything/mthood.m4a")
.build())
.guid("http://example.com/podcasts/archive/aae20170606.m4a")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2016, 6, 06, 12, 0, 0), ZoneId.of("GMT")))
.duration("17:04")
.explicit(false)
.build())
.item(ItunesPodcastEpisode.builder("Bouldering Around Boulder")
.episodeType(ItunesPodcastEpisodeType.FULL)
.episode(3)
.season(2)
.title("S02 EP03 Bouldering Around Boulder")
.author("The Sunset Adventurers")
.subtitle("We explore fun walks to climbing areas about the beautiful Colorado city of Boulder.")
.summary("We explore fun walks to climbing areas about the beautiful Colorado city of Boulder.")
.description("We explore fun walks to climbing areas about the beautiful Colorado city of Boulder.")
.image("http://example.com/podcasts/everything/AllAboutEverything/Episode2.jpg")
.contentEncoded("href=\"http://example.com/podcasts/everything/\"")
.enclosure(RssItemEnclosure.builder()
.length(5650889)
.type("video/mp4")
.url("http://example.com/podcasts/boulder.mp4")
.build())
.guid("http://example.com/podcasts/archive/aae20170530.mp4")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2017, 5, 30, 13, 0, 0), ZoneId.of("GMT")))
.duration("06:27")
.explicit(false)
.build())
.item(ItunesPodcastEpisode.builder("Caribou Mountain, Maine")
.episodeType(ItunesPodcastEpisodeType.FULL)
.episode(2)
.season(2)
.title("S02 EP02 Caribou Mountain, Maine")
.author("The Sunset Adventurers")
.subtitle("Put your fitness to the test with this invigorating hill climb.")
.summary("Put your fitness to the test with this invigorating hill climb.")
.description("Put your fitness to the test with this invigorating hill climb.")
.image("http://example.com/podcasts/everything/AllAboutEverything/Episode3.jpg")
.contentEncoded("href=\"http://example.com/podcasts/everything/\"")
.enclosure(RssItemEnclosure.builder()
.length(5650889)
.type("audio/x-m4v")
.url("http://example.com/podcasts/everything/caribou.m4v")
.build())
.guid("http://example.com/podcasts/archive/aae20170523.m4v")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2017, 5, 23, 02, 0, 0), ZoneId.of("GMT")))
.duration("04:34")
.explicit(false)
.build())
.item(ItunesPodcastEpisode.builder("Stawamus Chief")
.episodeType(ItunesPodcastEpisodeType.FULL)
.episode(1)
.season(2)
.title("S02 EP01 Stawamus Chief")
.author("The Sunset Adventurers")
.subtitle("We tackle Stawamus Chief outside of Vancouver, BC and you should too!")
.summary("We tackle Stawamus Chief outside of Vancouver, BC and you should too!")
.description("We tackle Stawamus Chief outside of Vancouver, BC and you should too!")
.contentEncoded("We tackle Stawamus Chief outside of Vancouver, BC and you should too!")
.enclosure(RssItemEnclosure.builder()
.length(498537)
.type("audio/mpeg")
.url("http://example.com/podcasts/everything/AllAboutEverythingEpisode4.mp3")
.build())
.guid("http://example.com/podcasts/archive/aae20170516.mp3")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2017, 5, 16, 02, 0, 0), ZoneId.of("GMT")))
.duration("13:24")
.explicit(false)
.build())
.item(ItunesPodcastEpisode.builder("Kuliouou Ridge Trail")
.episodeType(ItunesPodcastEpisodeType.FULL)
.episode(4)
.season(1)
.title("S01 EP04 Kuliouou Ridge Trail")
.author("The Sunset Adventurers")
.subtitle("Oahu, Hawaii, has some picturesque hikes and this is one of the best!")
.summary("Oahu, Hawaii, has some picturesque hikes and this is one of the best!")
.description("Oahu, Hawaii, has some picturesque hikes and this is one of the best!")
.contentEncoded("Oahu, Hawaii, has some picturesque hikes and this is one of the best!")
.enclosure(RssItemEnclosure.builder()
.length(498537)
.type("audio/mpeg")
.url("http://example.com/podcasts/everything/AllAboutEverythingEpisode4.mp3")
.build())
.guid("http://example.com/podcasts/archive/aae20160509.mp3")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2017, 5, 10, 01, 15, 0), ZoneId.of("GMT")))
.duration("15:29")
.explicit(false)
.build())
.item(ItunesPodcastEpisode.builder("Blood Mountain Loop")
.episodeType(ItunesPodcastEpisodeType.FULL)
.episode(3)
.season(1)
.title("S01 EP03 Blood Mountain Loop")
.author("The Sunset Adventurers")
.subtitle("Hiking the Appalachian Trail and Freeman Trail in Georgia")
.summary("Hiking the Appalachian Trail and Freeman Trail in Georgia")
.description("Hiking the Appalachian Trail and Freeman Trail in Georgia")
.contentEncoded("Hiking the Appalachian Trail and Freeman Trail in Georgia")
.enclosure(RssItemEnclosure.builder()
.length(498537)
.type("audio/mpeg")
.url("http://example.com/podcasts/everything/AllAboutEverythingEpisode4.mp3")
.build())
.guid("http://example.com/podcasts/archive/aae20160502.mp3")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2017, 5, 3, 01, 15, 0), ZoneId.of("GMT")))
.duration("24:59")
.explicit(false)
.build())
.item(ItunesPodcastEpisode.builder("Garden of the Gods Wilderness")
.episodeType(ItunesPodcastEpisodeType.FULL)
.episode(2)
.season(1)
.title("S01 EP02 Garden of the Gods Wilderness")
.author("The Sunset Adventurers")
.subtitle("Wilderness Area Garden of the Gods in Illinois is a delightful spot for an extended hike.")
.summary("Wilderness Area Garden of the Gods in Illinois is a delightful spot for an extended hike.")
.description("Wilderness Area Garden of the Gods in Illinois is a delightful spot for an extended hike.")
.contentEncoded("Wilderness Area Garden of the Gods in Illinois is a delightful spot for an extended hike.")
.enclosure(RssItemEnclosure.builder()
.length(498537)
.type("audio/mpeg")
.url("http://example.com/podcasts/everything/AllAboutEverythingEpisode4.mp3")
.build())
.guid("http://example.com/podcasts/archive/aae20160425.mp3")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2017, 4, 26, 01, 15, 0), ZoneId.of("GMT")))
.duration("13:59")
.explicit(false)
.build())
.item(ItunesPodcastEpisode.builder("Upper Priest Lake Trail to Continental Creek Trail")
.episodeType(ItunesPodcastEpisodeType.FULL)
.episode(1)
.season(1)
.title("S01 EP01 Upper Priest Lake Trail to Continental Creek Trail")
.author("The Sunset Adventurers")
.subtitle("We check out this powerfully scenic hike following the river in the Idaho Panhandle National Forests.")
.summary("We check out this powerfully scenic hike following the river in the Idaho Panhandle National Forests.")
.description("We check out this powerfully scenic hike following the river in the Idaho Panhandle National Forests.")
.contentEncoded("We check out this powerfully scenic hike following the river in the Idaho Panhandle National Forests.")
.enclosure(RssItemEnclosure.builder()
.length(498537)
.type("audio/mpeg")
.url("http://example.com/podcasts/everything/AllAboutEverythingEpisode4.mp3")
.build())
.guid("http://example.com/podcasts/archive/aae20160418a.mp3")
.pubDate(ZonedDateTime.of(LocalDateTime.of(2017, 4, 19, 01, 15, 0), ZoneId.of("GMT")))
.duration("23:59")
.explicit(false)
.contentEncoded("The Sunset Explorers share tips, techniques and recommendations for great hikes and adventures around the United States. Listen on <a href=\"https://www.apple.com/itunes/podcasts/\">Apple Podcasts</a>")
.build()).build());
}
@Override
@SingleResult
public Publisher<RssChannel> fetchById(Serializable id) {
return Publishers.empty();
}
}
7 JSON Feeds
JSON Feed is a format similar to RSS and Atom but in JSON.
The following dependency contains the core classes to build a JSON Feed.
implementation("io.micronaut.rss:micronaut-jsonfeed-core")
<dependency>
<groupId>io.micronaut.rss</groupId>
<artifactId>micronaut-jsonfeed-core</artifactId>
</dependency>
To get such JSON:
{
"version": "https://jsonfeed.org/version/1.1",
"title": "My Example Feed",
"home_page_url": "https://example.org/",
"feed_url": "https://example.org/feed.json",
"items": [
{
"id": "2",
"content_text": "This is a second item.",
"url": "https://example.org/second-item"
},
{
"id": "1",
"content_html": "<p>Hello, world!</p>",
"url": "https://example.org/initial-post"
}
]
}
You can write:
JsonFeed feed = JsonFeed.builder()
.version("https://jsonfeed.org/version/1.1")
.title("My Example Feed")
.homePageUrl("https://example.org/")
.feedUrl("https://example.org/feed.json")
.item(JsonFeedItem.builder()
.id("2")
.contentText("This is a second item.")
.url("https://example.org/second-item")
.build())
.item(JsonFeedItem.builder()
.id("1")
.contentHtml("<p>Hello, world!</p>")
.url("https://example.org/initial-post")
.build())
.build()
7.1 JSON Feeds Endpoint
The following dependency contains functionality to expose an endpoint to return a JSON Feed:
implementation("io.micronaut.rss:micronaut-jsonfeed")
<dependency>
<groupId>io.micronaut.rss</groupId>
<artifactId>micronaut-jsonfeed</artifactId>
</dependency>
Your JSON Feed will be of content type application/json+feed
.
You have to register an additional type for Micronaut’s json
codec:
micronaut.codec.json.additional-types[0]=application/json+feed
Then, if you provide a bean of type JsonFeedProvider such as:
@Singleton
public class ExampleJsonFeedProvider implements JsonFeedProvider {
@NonNull
@SingleResult
@Override
public Publisher<JsonFeed> feed(@Nullable Integer maxNumberOfItems, @Nullable Integer pageNumber) {
return Flux.create( emitter -> {
emitter.next(JsonFeed.builder()
.version("https://jsonfeed.org/version/1.1")
.title("My Example Feed")
.homePageUrl("https://example.org/")
.feedUrl("https://example.org/feed.json")
.item(JsonFeedItem.builder()
.id("2")
.contentText("This is a second item.")
.url("https://example.org/second-item")
.build())
.item(JsonFeedItem.builder()
.id("1")
.contentHtml("<p>Hello, world!</p>")
.url("https://example.org/initial-post")
.build())
.build());
emitter.complete();
}, FluxSink.OverflowStrategy.ERROR);
}
}
If the creation of the JSON feed is a blocking I/O operation, offload that tasks to a separate thread pool that does not block the Event loop. |
A GET request to /feeds/json
returns a 200 OK response with HTTP Header with name Content-Type
with value application/json+feed
and a JSON Payload in the body such as:
{ "version": "https://jsonfeed.org/version/1.1", "title": "My Example Feed", "home_page_url": "https://example.org/", "feed_url": "https://example.org/feed.json", "items": [ { "id": "2", "content_text": "This is a second item.", "url": "https://example.org/second-item" }, { "id": "1", "content_html": "<p>Hello, world!</p>", "url": "https://example.org/initial-post" } ] }
There are additional configuration options for the JSON Feed Controller:
Property | Type | Description |
---|---|---|
|
java.lang.String |
Configures JsonFeedController rootpath. Default value "/feeds" |
|
java.lang.String |
Configures JsonFeedController path. Default value "/json" |
|
boolean |
Whether JsonFeedController should be enabled. Default value (true). |
You can implement pagination by supplying maxNumberOfItems
and pageNumber
which will be passed to your implementation of JsonFeedProvider.
8 Repository
You can find the source code of this project in this repository: