Expand description
Introduction
Roa is an async web framework inspired by koajs, lightweight but powerful.
Application
A Roa application is a structure composing and executing middlewares and an endpoint in a stack-like manner.
The obligatory hello world application:
use roa::App;
use roa::preload::*;
use tracing::info;
use std::error::Error as StdError;
#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
let app = App::new().end("Hello, World");
app.listen("127.0.0.1:8000", |addr| {
info!("Server is listening on {}", addr)
})?
.await?;
Ok(())
}
Endpoint
An endpoint is a request handler.
There are some build-in endpoints in roa.
-
Functional endpoint
A normal functional endpoint is an async function with signature:
async fn(&mut Context) -> Result
.use roa::{App, Context, Result}; async fn endpoint(ctx: &mut Context) -> Result { Ok(()) } let app = App::new().end(endpoint);
-
Ok endpoint
()
is an endpoint always returnOk(())
let app = roa::App::new().end(());
-
Status endpoint
Status
is an endpoint always returnErr(Status)
use roa::{App, status}; use roa::http::StatusCode; let app = App::new().end(status!(StatusCode::BAD_REQUEST));
-
String endpoint
Write string to body.
use roa::App; let app = App::new().end("Hello, world"); // static slice let app = App::new().end("Hello, world".to_owned()); // string
-
Redirect endpoint
Redirect to an uri.
use roa::App; use roa::http::Uri; let app = App::new().end("/target".parse::<Uri>().unwrap());
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 endpoint. When a middleware invokes next the function suspends and passes control to the next middleware or endpoint. After the endpoint is called, the stack will unwind and each middleware is resumed to perform its upstream behaviour.
use roa::{App, Context, Next};
use roa::preload::*;
use tracing::info;
use std::error::Error as StdError;
use std::time::Instant;
#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
let app = App::new()
.gate(logger)
.gate(x_response_time)
.end("Hello, World");
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.load::<String>("x-response-time").unwrap();
info!("{} {} - {}", ctx.method(), ctx.uri(), rt.as_str());
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.store("x-response-time", format!("{}ms", ms));
Ok(())
}
Status Handling
You can catch or straightly throw a status returned by next.
use roa::{App, Context, Next, status};
use roa::preload::*;
use roa::http::StatusCode;
use tokio::task::spawn;
use tracing::info;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = App::new()
.gate(catch)
.gate(not_catch)
.end(status!(StatusCode::IM_A_TEAPOT, "I'm a teapot!"));
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 {
// catch
if let Err(status) = next.await {
// teapot is ok
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?; // just throw
unreachable!()
}
status_handler
App has an status_handler to handle status thrown by the top middleware. This is the status_handler:
use roa::{Context, Status};
pub fn status_handler<S>(ctx: &mut Context<S>, status: Status) {
ctx.resp.status = status.status_code;
if status.expose {
ctx.resp.write(status.message);
} else {
tracing::error!("{}", status);
}
}
Router.
Roa provides a configurable and nestable router.
use roa::preload::*;
use roa::router::{Router, get};
use roa::{App, Context};
use tokio::task::spawn;
use tracing::info;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let router = Router::new()
.on("/:id", get(end)); // get dynamic "/:id"
let app = App::new()
.end(router.routes("/user")?); // route with prefix "/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 {
// get "/user/1", then id == 1.
let id: u64 = ctx.must_param("id")?.parse()?;
// do something
Ok(())
}
Query
Roa provides a middleware query_parser
.
use roa::preload::*;
use roa::query::query_parser;
use roa::{App, Context};
use tokio::task::spawn;
use tracing::info;
async fn must(ctx: &mut Context) -> roa::Result {
// request "/?id=1", then id == 1.
let id: u64 = ctx.must_query("id")?.parse()?;
Ok(())
}
#[tokio::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 conveniently.
- compress: supports transparent content compression.
- cookie: cookies getter or setter.
- cors: CORS support.
- forward: “X-Forwarded-*” parser.
- jwt: json web token support.
- logger: a logger middleware.
- tls: https supports.
- websocket: websocket supports.
Modules
This module provides a context extension PowerBody
.
compress
This module provides a middleware Compress
.
cookies
This module provides a middleware cookie_parser
and context extensions CookieGetter
and CookieSetter
.
This module provides a middleware Cors
.
This module provides a context extension Forward
,
which is used to parse X-Forwarded-*
headers.
A general purpose library of common HTTP types
jsonrpc
roa::jsonrpc
jwt
This module provides middleware JwtGuard
and a context extension JwtVerifier
.
This module provides a middleware logger
.
Reexport all extension traits.
This module provides a middleware query_parser
and a context extension Query
.
router
This module provides a context extension RouterParam
and
many endpoint wrappers like Router
, Dispatcher
and Guard
.
this module provides a stream adaptor AsyncStream
tcp
This module provides an acceptor implementing roa_core::Accept
and an app extension.
tls
This module provides an acceptor implementing roa_core::Accept
and an app extension.
websocket
This module provides a websocket endpoint.
Macros
Structs
A transport returned yieled by AddrIncoming
.
The Application of roa.
Boxed endpoint.
A middleware composing and executing other middlewares in a stack-like manner.
A structure to share request, response and other data between middlewares.
A type implementing hyper::rt::Executor
A handle that awaits the result of a task.
Http request type of roa.
Http response type of roa.
A listening HTTP server that accepts connections in both HTTP1 and HTTP2 by default.
Shared middleware.
The Status
of roa.
A wrapper of Arc.
Enums
The body of response.
Traits
Asynchronously accept incoming connections.
Endpoint
Extra methods of endpoint.
Middleware
A set of method to chain middleware/endpoint to middleware or make middleware shared.
Executor constraint.
The State
trait, should be replace with trait alias.
The App::state
will be cloned when a request inbounds.
Type Definitions
Type of the second parameter in a middleware,
an alias for &mut (dyn Unpin + Future<Output = Result>)
Type alias for StdResult
.