-
Notifications
You must be signed in to change notification settings - Fork 12
Cookbook
App is the entry point of roa application, it registers middlewares and endpoint, then serves as a http server.
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());
-
listen
Try to listen on a socket addr, return a server, and pass real addr to the callback.
use roa::App; use roa::preload::*; use std::error::Error; #[async_std::main] async fn main() -> Result<(), Box<dyn Error>> { App::new() .end("Hello, world") .listen("127.0.0.1:8000", |addr| { println!("Server is listening on {}", addr) })? .await?; Ok(()) }
-
bind
Try to listen on a socket addr, return a server and the real addr it binds.
use roa::App; use roa::preload::*; use std::error::Error; #[async_std::main] async fn main() -> Result<(), Box<dyn Error>> { let (addr, server) = App::new() .end("Hello, world") .bind("127.0.0.1:8000")?; println!("Server is listening on {}", addr); server.await?; Ok(()) }
-
run
Try to listen on a random unused port of 127.0.0.1, return a server and the real addr it binds.
use roa::App; use roa::preload::*; use std::error::Error; #[async_std::main] async fn main() -> Result<(), Box<dyn Error>> { let (addr, server) = App::new() .end("Hello, world") .run()?; println!("Server is listening on {}", addr); server.await?; Ok(()) }
The status of response is 200 OK
by default. Roa provides two ways to change it.
-
Normally set status code
use roa::{Context, Result}; use roa::http::StatusCode; async fn create(ctx: &mut Context) -> Result { ctx.resp.status = StatusCode::CREATED; Ok(()) }
-
Throw status
You can throw a status to fail fast.
use roa::{Context, Result, throw}; use roa::http::StatusCode; async fn create(ctx: &mut Context) -> Result { throw!(StatusCode::BAD_REQUEST) }
Errors will be converted to
500 INTERNAL SERVER ERROR
by default:use roa::{Context, Result}; async fn create(ctx: &mut Context) -> Result { let id: usize = "x".parse()?; // fail, throw 500 INTERNAL SERVER ERROR Ok(()) }
You can straightly access method
, uri
and version
of request by field name or context method.
use roa::{Context, Result};
async fn handle(ctx: &mut Context) -> Result {
let method = ctx.method();
let method = &ctx.req.method;
let uri = ctx.uri();
let uri = &ctx.req.uri;
let version = ctx.version();
let version = &ctx.req.version;
unimplemented!()
}
-
Header map
You can get raw http
HeaderMap
of request by field name.use roa::{Context, Result, throw}; use roa::http::header::HOST; use roa::http::StatusCode; async fn handle(ctx: &mut Context) -> Result { // get a string value let host = match ctx.req.headers.get(HOST).map(|value| value.to_str()) { Some(Ok(host)) => host, _ => throw!(StatusCode::BAD_REQUEST), }; unimplemented!() }
Raw header map is a little difficult to use, it's not a good choice to deal with header value which is a pure string.
-
Context::get
The another choice is the
get
andmust_get
method of context:use roa::{Context, Result, throw}; use roa::http::header::HOST; use roa::http::StatusCode; async fn handle(ctx: &mut Context) -> Result { // try get a string value let host = match ctx.get(HOST) { Some(host) => host, _ => throw!(StatusCode::BAD_REQUEST), }; // must get, otherwise throw a 400 BAD REQUEST let host = ctx.must_get(HOST)?; unimplemented!() }
-
Reader
Read body as
futures::io::AsyncRead
.use roa::{Context, Result}; use futures::AsyncReadExt; async fn handle(ctx: &mut Context) -> Result { let mut data = String::new(); ctx.req.reader().read_to_string(&mut data).await?; unimplemented!() }
-
Stream
Read body as
futures::stream::Stream<Item = io::Result<Bytes>>
.use roa::{Context, Result}; // an echo endpoint async fn echo(ctx: &mut Context) -> Result { let stream = ctx.req.stream(); ctx.resp.write_stream(stream); unimplemented!() }
-
PowerBody
roa::body
provides a extension traitPowerBody
to read and write body more conveniently.use roa::{Context, Result}; use roa::preload::*; use serde::Deserialize; #[derive(Deserialize)] struct User { id: u64, name: String, } // an echo endpoint async fn handle(ctx: &mut Context) -> Result { let data = ctx.read().await?; // get Vec<u8> let user: User = ctx.read_json().await?; // deserialize from json let user: User = ctx.read_form().await?; // deserialize from urlencoded form unimplemented!() }
roa::query
provides a middleware query_parser
and a extension Query
to parse query and get query pairs.
use roa::query::query_parser;
use roa::{App, Context, Result};
// import extension Query
use roa::preload::*;
async fn handle(ctx: &mut Context) -> Result {
assert_eq!("Hexilee", &*ctx.must_query("name")?);
Ok(())
}
let app = App::new().gate(query_parser).end(handle);
// get `/?name=Hexilee`
You can straightly access status
and version
of response by field name.
use roa::{Context, Result};
use roa::http::{StatusCode, Version};
async fn handle(ctx: &mut Context) -> Result {
ctx.resp.status = StatusCode::CREATED;
ctx.resp.version = Version::HTTP_11;
unimplemented!()
}
You can access the header map of response by field name.
use roa::{Context, Result, throw};
use roa::http::header::LOCATION;
use roa::http::{StatusCode, HeaderValue};
async fn handle(ctx: &mut Context) -> Result {
// parse
let location = "/target".parse()?;
// or static parse
// let location = HeaderValue::from_static("/target");
ctx.resp.headers.insert(LOCATION, location);
// redirect
throw!(StatusCode::PERMANENT_REDIRECT)
}
Refer to HeaderMap docs for more details.
roa_core
provides several methods to write body.
use roa::{Context, Result};
use futures::AsyncReadExt;
use futures::io::BufReader;
use async_std::fs::File;
async fn get(ctx: &mut Context) -> Result {
ctx.resp
// write stream; echo
.write_stream(ctx.req.stream())
// write object implementing futures::AsyncRead
.write_reader(File::open("assets/author.txt").await?)
// write reader with specific chunk size
.write_chunk(File::open("assets/author.txt").await?, 1024)
// write text
.write("I am Roa.")
.write(b"I am Roa.".as_ref());
Ok(())
}
These methods are useful, but they do not deal with headers and serialization.
roa::body
provides extension PowerBody
to handle it.
use roa::{Context, Result};
use roa::body::DispositionType::*;
use roa::preload::*;
use serde::{Serialize, Deserialize};
use askama::Template;
use async_std::fs::File;
#[derive(Debug, Serialize, Deserialize, Template)]
#[template(path = "user.html")]
struct User {
id: u64,
name: String,
}
async fn get(ctx: &mut Context) -> Result {
// serialize object and write it to body,
// set "Content-Type" = "application/json"
// require `feature = "json"`
ctx.write_json(&user)?;
// open file and write it to body,
// set "Content-Type" and "Content-Disposition"
// require `feature = "file"`
ctx.write_file("assets/welcome.html", Inline).await?;
// write text,
// set "Content-Type" = "text/plain"
ctx.write("Hello, World!");
// write object implementing AsyncRead,
// set "Content-Type" = "application/octet-stream"
ctx.write_reader(File::open("assets/author.txt").await?);
// render html template, based on [askama](https://github.com/djc/askama)
// set "Content-Type" = "text/html; charset=utf-8"
// require `feature = "template"`
ctx.render(&user)?;
Ok(())
}
roa::router
provides a configurable and nestable router.
use roa::{Context, Result, App};
use roa::router::{Router, get, allow, deny};
use roa::query::query_parser;
use roa::http::Method;
async fn create(ctx: &mut Context) -> Result {
unimplemented!()
}
let router = Router::new()
.gate(query_parser) // use middleware
.on("/hello", "Hello, world") // allow all http method on /hello
.on("/resources", allow([Method::GET, Method::POST], "Hello, world")) // allow get and post
.on("/dangerous", deny([Method::POST, Method::PUT], create)) // deny post and put
.on("/api", get("Hello, world").post(create)); // dispatch by http method
let app = App::new().end(router.routes("/prefix").unwrap());
Routers can route on dynamic path.
use roa::{Context, Result, App};
use roa::router::Router;
use roa::preload::*;
async fn segment_variable(ctx: &mut Context) -> Result {
let name = ctx.must_param("file")?;
unimplemented!()
}
async fn wildcard(ctx: &mut Context) -> Result {
let path = ctx.must_param("file")?;
unimplemented!()
}
// segment variable
// one variable can only match one path segment.
// "/welcome.html" is matched.
// "/dir/welcome.html" is not matched.
let router = Router::new().
on("/:file", segment_variable);
// wildcard variable
// "/welcome.html" is matched.
// "/dir/welcome.html" is also matched.
let router = Router::new().
on("/*{file}", wildcard);
Routers can be nested.
use roa::{Context, Result, App};
use roa::router::{Router, get};
use roa::preload::*;
use serde_json::json;
async fn query(ctx: &mut Context) -> Result {
let id: u64 = ctx.must_param("id")?.parse()?;
ctx.write_json(&json!({"id": id}))
}
let user_router = Router::new().on("/:id", get(query));
// include
let router = Router::new().include("/user", user_router);
let app = App::new().end(router.routes("/").unwrap());
// request: /user/0 => {"id": 0}
Roa provides two solutions for authentication.
roa::cookie
provides a middleware cookie_parser
and context extensions CookieSetter
and CookieGetter
.
feature = "cookies"
is required.roa = { version = "0.5.0", features = ["cookies"] }
- Get cookie
use roa::cookie::cookie_parser;
use roa::{App, Result, Context};
use roa::preload::*;
use roa::http::StatusCode;
use roa::http::header::COOKIE;
async fn handle(ctx: &mut Context) -> Result {
let optional_name = ctx.cookie("name"); // get Option<Cookie>
let name = ctx.must_cookie("name")?; // must get, otherwise throw 401 UNAUTHORIZED
unimplemented!()
}
let app = App::new()
.gate(cookie_parser)
.end(handle);
- Set cookie
use roa::cookie::Cookie;
use roa::{Context, Result};
use roa::preload::*;
async fn login(ctx: &mut Context) -> Result {
// set cookie in percent encoding
// name=Hexi%20Lee
ctx.set_cookie(Cookie::new("name", "Hexi Lee"))?;
unimplemented!()
}
roa::jwt
module provides a middleware JwtGuard
and a context extension JwtVerifier
.
feature = "jwt"
is required.roa = { version = "0.5.0", features = ["jwt"] }
use roa::App;
use roa::jwt::{guard, DecodingKey};
const SECRET: &[u8] = b"123456";
let app = App::new()
.gate(guard(DecodingKey::from_secret(SECRET)))
.end("Hello, world");
The guard
function receive a secret and return a middleware to guard its downstream.
The json web token should be deliver by request header "authorization", in format of Authorization: Bearer <token>
.
- Get claims
You can add use roa::jwt::JwtVerifier
or use roa::preload::*
to use this context extension.
Attention please, this extension must be used in downstream of guard, otherwise you cannot get expected claims.
use roa::{Context, Result};
use roa::preload::*;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
sub: String,
company: String,
exp: u64,
id: u64,
name: String,
}
async fn get(ctx: &mut Context) -> Result {
let user: User = ctx.claims()?;
// do something
Ok(())
}
- Configured validation
You can configure Validation to set validation options.
use roa::App;
use roa::jwt::{JwtGuard, DecodingKey, Validation};
const SECRET: &[u8] = b"123456";
let validation = Validation {leeway: 60, ..Default::default()};
let decoding_key = DecodingKey::from_secret(SECRET);
let app = App::new()
.gate(JwtGuard::new(decoding_key, validation))
.end("Hello, world");
The JwtVerifier
also provides method verify
to verify token by custom validation:
use roa::{Context, Result};
use roa::jwt::Validation;
use roa::preload::*;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
sub: String,
company: String,
exp: u64,
id: u64,
name: String,
aud: Vec<String>,
}
// this endpoint can only be accessed by admin
async fn delete_user(ctx: &mut Context) -> Result {
let mut validation = Validation::default();
validation.set_audience(&["admin"]);
let user: User = ctx.verify(&validation)?;
unimplemented!()
}
roa::compress
provides a transparent content compression middleware.
feature = "compress"
is required.roa = { version = "0.5.0", features = ["compress"] }
use roa::App;
use roa::compress::{Compress, Level};
let app = App::new()
.gate(Compress(Level::Fastest))
.end("Hello, world");
It will negotiate with client and compress response body automatically, supports gzip, deflate, brotli, zstd and identity.
The roa::logger
provides a middleware logger
.
use roa::App;
use roa::logger::logger;
let mut app = App::new();
// just use it
app.gate(logger);
logger
middleware to log information about request and response.
Based on crate log
, the log level must be greater than INFO
to log all information,
and should be greater than ERROR
when you need error information only.
A middleware to deal with Cross-Origin Resource Sharing (CORS).
- Default
The default Cors middleware will satisfy all needs of a request.
Build a default Cors middleware:
use roa::App;
use roa::cors::Cors;
let app = App::new()
.gate(Cors::new())
.end("Hello, world");
- Config
You can also configure it:
use roa::cors::Cors;
use roa::http::header::{CONTENT_DISPOSITION, AUTHORIZATION, WWW_AUTHENTICATE};
use roa::http::Method;
let cors = Cors::builder()
.allow_credentials(true)
.max_age(86400)
.allow_origin("https://github.com")
.allow_methods(vec![Method::GET, Method::POST])
.allow_method(Method::PUT)
.expose_headers(vec![CONTENT_DISPOSITION])
.expose_header(WWW_AUTHENTICATE)
.allow_headers(vec![AUTHORIZATION])
.allow_header(CONTENT_DISPOSITION)
.build();
roa::forward
provides a context extension Forward
to parse X-Forwarded-*
request headers.
-
host
- If
"x-forwarded-host"
is set and valid, use it. - Else if
"host"
is set and valid, use it.
- If
use roa::{Context, Result};
use roa::preload::*;
async fn get(ctx: &mut Context) -> Result {
if let Some(host) = ctx.host() {
println!("host: {}", host);
}
Ok(())
}
-
client_ip
- If
"x-forwarded-for"
is set and valid, use the first ip. - Else use the ip addr of
ctx.remote_addr
.
- If
use roa::{Context, Result};
use roa::preload::*;
async fn get(ctx: &mut Context) -> Result {
println!("client ip: {}", ctx.client_ip());
Ok(())
}
- forwarded_ips
use roa::{Context, Result};
use roa::forward::Forward;
async fn get(ctx: Context<()>) -> Result {
println!("forwarded ips: {:?}", ctx.forwarded_ips());
Ok(())
}
- forwarded_proto
use roa::{Context, Result};
use roa::forward::Forward;
async fn get(ctx: Context<()>) -> Result {
if let Some(proto) = ctx.forwarded_proto() {
println!("forwarded proto: {}", proto);
}
Ok(())
}
-
Functional middleware
A functional middleware is an async function with signature:
async fn(&mut Context, Next<'_>) -> Result
.use roa::{App, Context, Next, Result}; async fn middleware(ctx: &mut Context, next: Next<'_>) -> Result { next.await } let app = App::new().gate(middleware);
-
Custom middleware
You can implement custom
Middleware
for other types.use roa::{App, Middleware, Context, Next, Result, async_trait}; use std::sync::Arc; use std::time::Instant; struct Logger; #[async_trait(?Send)] impl <'a> Middleware<'a> for Logger { async fn handle(&'a self, ctx: &'a mut Context, next: Next<'a>) -> Result { let start = Instant::now(); let result = next.await; println!("time elapsed: {}ms", start.elapsed().as_millis()); result } } let app = App::new().gate(Logger);
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 std::time::Instant;
use log::info;
let app = App::new()
.gate(logger)
.gate(x_response_time)
.end("Hello, World");
async fn logger(ctx: &mut Context, next: Next<'_>) -> roa::Result {
next.await?;
let rt = ctx.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.headers.insert("x-response-time", format!("{}ms", ms).parse()?);
Ok(())
}
You can catch or straightly throw a status returned by next.
use roa::{App, Context, Next, status};
use roa::preload::*;
use roa::http::StatusCode;
let app = App::new()
.gate(catch)
.gate(not_catch)
.end(status!(StatusCode::IM_A_TEAPOT, "I'm a teapot!"));
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!()
}
- Uncaught status
There is an status handler to handle uncaught status in app.
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 {
log::error!("{}", status);
}
}
Roa provides two solutions to pass data between middlewares and endpoint.
Use App::state
method to initialize an app with a state.
The app.state
will be cloned and passed by context when a request inbounds.
You can access state by context,
use roa::{App, Context, Next, Result};
use roa::http::StatusCode;
#[derive(Clone)]
struct State {
id: u64,
}
let app = App::state(State { id: 0 }).gate(gate).end(end);
async fn gate(ctx: &mut Context<State>, next: Next<'_>) -> Result {
ctx.id = 1;
next.await
}
async fn end(ctx: &mut Context<State>) -> Result {
let id = ctx.id;
assert_eq!(1, id);
Ok(())
}
There is an individual storage in each context, you can store or load any data in it.
use roa::{App, Context, Result, Next};
struct Data(i32);
async fn gate(ctx: &mut Context, next: Next<'_>) -> Result {
ctx.store("id", Data(1));
next.await
}
async fn end(ctx: &mut Context) -> Result {
assert_eq!(1, ctx.load::<Data>("id").unwrap().0);
Ok(())
}
let app = App::new().gate(gate).end(end);
-
scope
You can store data in a private scope, then other middlewares or endpoint cannot access it straightly.
use roa::{App, Context, Result, Next}; struct Scope; struct Data(i32); async fn gate(ctx: &mut Context, next: Next<'_>) -> Result { ctx.store_scoped(Scope, "id", Data(1)); next.await } async fn end(ctx: &mut Context) -> Result { assert_eq!(1, ctx.load_scoped::<Scope, Data>("id").unwrap().0); Ok(()) } let app = App::new().gate(gate).end(end);
The roa-diesel crate provides integration of diesel, please refer to docs or integration example for more details.
The roa-pg crate provides integration of tokio-postgres. A simple query service:
use roa::{App, Context, throw};
use roa::http::StatusCode;
use roa_pg::{connect, Client};
use std::sync::Arc;
use std::error::Error;
use roa::query::query_parser;
use roa::preload::*;
use async_std::task::spawn;
#[derive(Clone)]
struct State {
pg: Arc<Client>
}
impl State {
pub async fn new(pg_url: &str) -> Result<Self, Box<dyn Error>> {
let (client, conn) = connect(&pg_url.parse()?).await?;
spawn(conn);
Ok(Self {pg: Arc::new(client)})
}
}
async fn query(ctx: &mut Context<State>) -> roa::Result {
let id: u32 = ctx.must_query("id")?.parse()?;
match ctx.pg.query_opt("SELECT * FROM user WHERE id=$1", &[&id]).await? {
Some(row) => {
let value: String = row.get(0);
ctx.write(value);
Ok(())
}
None => throw!(StatusCode::NOT_FOUND),
}
}
#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
let url = "postgres://fred:secret@localhost/test";
let state = State::new(url).await?;
App::state(state)
.gate(query_parser)
.end(query)
.listen("127.0.0.1:0", |addr| {
println!("Server is listening on {}", addr)
})?.await?;
Ok(())
}
The roa-multipart crate provides support for multipart form. A file uploading service:
use async_std::fs::File;
use async_std::io;
use async_std::path::Path;
use futures::stream::TryStreamExt;
use futures::StreamExt;
use roa::http::StatusCode;
use roa::tcp::Listener;
use roa::router::{Router, post};
use roa::{throw, App, Context};
use roa_multipart::MultipartForm;
use std::error::Error as StdError;
async fn post_file(ctx: &mut Context) -> roa::Result {
let mut form = ctx.form();
while let Some(item) = form.next().await {
let field = item?;
match field.content_disposition() {
None => throw!(StatusCode::BAD_REQUEST, "content disposition not set"),
Some(content_disposition) => match content_disposition.get_filename() {
None => continue, // ignore non-file field
Some(filename) => {
let path = Path::new("./upload");
let mut file = File::create(path.join(filename)).await?;
io::copy(&mut field.into_async_read(), &mut file).await?;
}
},
}
}
Ok(())
}
#[async_std::main]
async fn main() -> Result<(), Box<dyn StdError>> {
let router = Router::new().on("/file", post(post_file));
App::new()
.end(router.routes("/")?)
.listen("127.0.0.1:8000", |addr| {
println!("Server is listening on {}", addr);
})?
.await?;
Ok(())
}
The roa-juniper crate provides integration of juniper. However, you cannot get it from crates.io until juniper v0.14.3 is published.
You can preview this feature in integration-example.
Roa supports websocket when feature = "websocket"
is enabled. An echo service:
use futures::StreamExt;
use roa::http::Method;
use log::{error, info};
use roa::cors::Cors;
use roa::logger::logger;
use roa::preload::*;
use roa::router::{allow, Router};
use roa::websocket::Websocket;
use roa::App;
use std::error::Error as StdError;
#[async_std::main]
async fn main() -> Result<(), Box<dyn StdError>> {
let router = Router::new().on(
"/chat",
allow(
[Method::GET],
Websocket::new(|_ctx, stream| async move {
let (write, read) = stream.split();
if let Err(err) = read.forward(write).await {
error!("forward err: {}", err);
}
}),
),
);
let app = App::new()
.gate(logger)
.gate(Cors::new())
.end(router.routes("/")?);
app.listen("127.0.0.1:8000", |addr| {
info!("Server is listening on {}", addr)
})?
.await?;
Ok(())
}
Roa supports tls when feature = "tls"
is enabled, you can start a https server like this:
use roa::App;
use roa::tls::{TlsIncoming, ServerConfig, NoClientAuth, TlsListener};
use roa::tls::internal::pemfile::{certs, rsa_private_keys};
use std::fs::File;
use std::io::BufReader;
use std::error::Error;
#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
let mut config = ServerConfig::new(NoClientAuth::new());
let mut cert_file = BufReader::new(File::open("path/to/cert.pem")?);
let mut key_file = BufReader::new(File::open("path/to/key.pem")?);
let cert_chain = certs(&mut cert_file).unwrap();
let mut keys = rsa_private_keys(&mut key_file).unwrap();
config.set_single_cert(cert_chain, keys.remove(0))?;
let (addr, server) = App::new()
.end("Hello, world")
.bind_tls("127.0.0.1:0", config)?;
println!("Server is listening on {}", addr);
server.await?;
Ok(())
}
-
Async-std
Roa framework use async runtime and TcpStream of async-std by default. You can disable this feature in Cargo.toml.
roa = { version = "0.5.0", default-features = false }
-
Tokio
The roa-tokio crate provides tokio-based async runtime and TcpStream, you can use it like this:
use roa::http::StatusCode; use roa::{App, Context}; use roa_tokio::{TcpIncoming, Exec}; use std::error::Error; async fn end(_ctx: &mut Context) -> roa::Result { Ok(()) } #[tokio::main] async fn main() -> Result<(), Box<dyn Error>> { let app = App::with_exec((), Exec).end(end); let incoming = TcpIncoming::bind("127.0.0.1:0")?; println!("server is listening on {}", incoming.local_addr()); app.accept(incoming).await?; Ok(()) }
You can access the global executor by context.
use roa::{Context, Result};
async fn endpoint(ctx: &mut Context) -> Result {
let id = ctx.exec.spawn(async {1i32}).await;
let words = ctx.exec.spawn_blocking(|| "Hello, world!").await;
Ok(())
}
Roa app runs on hyper::Server
, which supports graceful shutdown.
use roa::App;
use roa::preload::*;
use futures::channel::oneshot;
use std::error::Error;
#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Prepare some signal for when the server should start shutting down...
let (tx, rx) = oneshot::channel::<()>();
App::new()
.end("Hello, world")
.listen("127.0.0.1:8000", |addr| {
println!("Server is listening on {}", addr)
})?
.with_graceful_shutdown(async {
rx.await.ok();
})
.await?;
// Await the `server` receiving the signal...
// And later, trigger the signal by calling `tx.send(())`.
let _ = tx.send(());
Ok(())
}