#![expect(
clippy::expect_used,
reason = "example/test/bench: panic-on-error and print-for-output are the standard patterns for demos and harnesses"
)]
use std::{sync::Arc, time::Duration};
use jiff::Timestamp;
use rama::{
Layer,
http::{
layer::{error_handling::ErrorHandlerLayer, trace::TraceLayer},
protocols::html::{IntoHtml, p},
protocols::rss::{
AtomContent, AtomEntry, AtomFeed, AtomLink, AtomPerson, AtomText, Rss2Feed, Rss2Guid,
Rss2Item,
feed_ext::{Content, ItemExtensions},
},
server::HttpServer,
service::web::{Router, response::IntoResponse},
},
net::{address::SocketAddress, uri::Uri},
rt::Executor,
tcp::server::TcpListener,
telemetry::tracing::{
self,
level_filters::LevelFilter,
subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt},
},
};
async fn rss2_feed() -> impl IntoResponse {
Rss2Feed::builder()
.title("The Rama Blog")
.link(Uri::from_static("https://ramaproxy.org/blog"))
.description("News and articles from the Rama project")
.with_language("en")
.with_generator("rama/http_rss_blog example")
.with_items(BLOG_POSTS.iter().map(|p| {
Rss2Item::new()
.with_title(p.title)
.with_link(Uri::from_static(p.url))
.with_description(p.summary)
.with_author("team@ramaproxy.org")
.with_guid(Rss2Guid::permalink(p.url))
.with_extensions(ItemExtensions {
content: Some(Box::new(Content {
encoded: Some(p!(p.body).into_string()),
})),
..Default::default()
})
}))
.build()
}
async fn atom_feed() -> impl IntoResponse {
let ts = Timestamp::now();
AtomFeed::builder()
.id(Uri::from_static("https://ramaproxy.org/feed.atom"))
.title("The Rama Blog")
.updated(ts)
.with_author(AtomPerson::new("Rama Team").with_email("team@ramaproxy.org"))
.with_link(AtomLink::alternate(Uri::from_static(
"https://ramaproxy.org/blog",
)))
.with_link(AtomLink::self_link(Uri::from_static(
"https://ramaproxy.org/feed.atom",
)))
.with_subtitle(AtomText::text("News and articles from the Rama project"))
.with_entries(BLOG_POSTS.iter().map(|p| {
AtomEntry::new(Uri::from_static(p.url), p.title, ts)
.with_author(AtomPerson::new("Rama Team"))
.with_link(AtomLink::alternate(Uri::from_static(p.url)))
.with_summary(AtomText::text(p.summary))
.with_content(AtomContent::html(p!(p.body)))
}))
.build()
}
#[tokio::main]
async fn main() {
tracing::subscriber::registry()
.with(fmt::layer())
.with(
EnvFilter::builder()
.with_default_directive(LevelFilter::DEBUG.into())
.from_env_lossy(),
)
.init();
let graceful = rama::graceful::Shutdown::default();
let exec = Executor::graceful(graceful.guard());
let listener = TcpListener::bind_address(SocketAddress::default_ipv4(62055), exec.clone())
.await
.expect("bind address");
let bind_address = listener.local_addr().expect("local addr");
tracing::info!(
network.local.address = %bind_address.ip(),
network.local.port = %bind_address.port(),
"http rss blog listening",
);
tracing::info!(
"fetch rss: curl http://{bind_address}/feed.rss | atom: curl http://{bind_address}/feed.atom"
);
graceful.spawn_task(async move {
let middlewares = (TraceLayer::new_for_http(), ErrorHandlerLayer::new());
let app = middlewares.into_layer(Arc::new(
Router::new()
.with_get("/feed.rss", rss2_feed)
.with_get("/feed.atom", atom_feed),
));
listener.serve(HttpServer::auto(exec).service(app)).await;
});
graceful
.shutdown_with_limit(Duration::from_secs(30))
.await
.expect("graceful shutdown");
}
struct BlogPost {
title: &'static str,
url: &'static str,
summary: &'static str,
body: &'static str,
}
static BLOG_POSTS: &[BlogPost] = &[
BlogPost {
title: "Introducing Rama",
url: "https://ramaproxy.org/blog/introducing-rama",
summary: "An overview of the Rama modular service framework.",
body: "Rama is a modular service framework for building proxies and web services in Rust.",
},
BlogPost {
title: "RSS Support in Rama",
url: "https://ramaproxy.org/blog/rss-support",
summary: "How to serve and consume RSS 2.0 and Atom 1.0 feeds with Rama.",
body: "Rama now ships built-in RSS 2.0 and Atom 1.0 support with type-state builders, extensions, and streaming writers.",
},
BlogPost {
title: "Streaming HTTP Responses",
url: "https://ramaproxy.org/blog/streaming-responses",
summary: "A guide to streaming large HTTP responses using Rama.",
body: "Rama provides first-class streaming support for SSE, ndjson, and now RSS/Atom feeds.",
},
];