Introduction
Roa is an async web framework inspired by koajs, lightweight but powerful.
Application
A Roa application is a structure containing a middleware group
which composes and executes middleware functions in a stack-like manner.
The obligatory hello world application:
use roa::{App, Context};
use roa::preload::*;
use log::info;
use std::error::Error as StdError;
async fn end(ctx: &mut Context<()>) -> roa::Result {
ctx.write_text("Hello, World");
Ok(())
}
#[async_std::main]
async fn main() -> Result<(), Box<dyn StdError>> {
let app = App::new(()).end(end);
app.listen("127.0.0.1:8000", |addr| {
info!("Server is listening on {}", addr)
})?
.await?;
Ok(())
}
Cascading
Like koajs, middleware suspends and passes control to "downstream" by invoking next.await
.
Then control flows back "upstream" when next.await
returns.
The following example responds with "Hello World",
however first the request flows through the x-response-time and logging middleware to mark
when the request started, then continue to yield control through the response middleware.
When a middleware invokes next the function suspends and passes control to the next middleware defined.
After there are no more middleware to execute downstream,
the stack will unwind and each middleware is resumed to perform its upstream behaviour.
use roa::{App, Context, Next};
use roa::preload::*;
use log::info;
use std::error::Error as StdError;
use std::time::Instant;
#[async_std::main]
async fn main() -> Result<(), Box<dyn StdError>> {
let app = App::new(())
.gate(logger)
.gate(x_response_time)
.end(response);
app.listen("127.0.0.1:8000", |addr| {
info!("Server is listening on {}", addr)
})?
.await?;
Ok(())
}
async fn logger(ctx: &mut Context<()>, next: Next<'_>) -> roa::Result {
next.await?;
let rt = ctx.resp.must_get("x-response-time")?;
info!("{} {} - {}", ctx.method(), ctx.uri(), rt);
Ok(())
}
async fn x_response_time(ctx: &mut Context<()>, next: Next<'_>) -> roa::Result {
let start = Instant::now();
next.await?;
let ms = start.elapsed().as_millis();
ctx.resp.insert("x-response-time", format!("{}ms", ms))?;
Ok(())
}
async fn response(ctx: &mut Context<()>) -> roa::Result {
ctx.write_text("Hello, World");
Ok(())
}
Error Handling
You can catch or straightly throw an error returned by next.
use roa::{App, Context, Next, throw};
use roa::preload::*;
use roa::http::StatusCode;
use async_std::task::spawn;
use log::info;
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = App::new(())
.gate(catch)
.gate(not_catch)
.end(error);
app.listen("127.0.0.1:8000", |addr| {
info!("Server is listening on {}", addr)
})?
.await?;
Ok(())
}
async fn catch(_ctx: &mut Context<()>, next: Next<'_>) -> roa::Result {
if let Err(status) = next.await {
if status.status_code != StatusCode::IM_A_TEAPOT {
return Err(status);
}
}
Ok(())
}
async fn not_catch(ctx: &mut Context<()>, next: Next<'_>) -> roa::Result {
next.await?; unreachable!()
}
async fn error(ctx: &mut Context<()>) -> roa::Result {
throw!(StatusCode::IM_A_TEAPOT, "I'm a teapot!")
}
status_handler
App has an status_handler to handle status thrown by the top middleware.
This is the status_handler:
use roa::{Context, Status, Result, State};
pub async fn status_handler<S: State>(ctx: &mut Context<S>, status: Status) -> Result {
ctx.resp.status = status.status_code;
if status.expose {
ctx.resp.write(status.message.clone());
}
if status.status_code.as_u16() / 100 == 5 {
Err(status)
} else {
Ok(())
}
}
The status thrown by this status_handler will be handled by hyper.
Router.
Roa provides a configurable and nestable router.
use roa::preload::*;
use roa::router::{Router, get};
use roa::{App, Context};
use async_std::task::spawn;
use log::info;
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let router = Router::new()
.on("/:id", get(end)); let app = App::new(())
.end(router.routes("/user")?); app.listen("127.0.0.1:8000", |addr| {
info!("Server is listening on {}", addr)
})?
.await?;
Ok(())
}
async fn end(ctx: &mut Context<()>) -> roa::Result {
let id: u64 = ctx.must_param("id")?.parse()?;
Ok(())
}
Query
Roa provides a middleware query_parser
.
use roa::preload::*;
use roa::query::query_parser;
use roa::{App, Context};
use async_std::task::spawn;
use log::info;
async fn must(ctx: &mut Context<()>) -> roa::Result {
let id: u64 = ctx.must_query("id")?.parse()?;
Ok(())
}
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = App::new(())
.gate(query_parser)
.end(must);
app.listen("127.0.0.1:8080", |addr| {
info!("Server is listening on {}", addr)
})?
.await?;
Ok(())
}
Other modules
- body: dealing with body more conviniently.
- compress: supports transparent content compression.
- cors: CORS support.
- forward: "X-Forwarded-*" parser.
- header: dealing with headers more conviniently.
- jwt: json web token support.
- logger: a logger middleware.