Crate viz

Source
Expand description

Viz

Fast, robust, flexible, lightweight web framework for Rust.

§Features

§Hello Viz

use std::net::SocketAddr;
use tokio::net::TcpListener;
use viz::{serve, Request, Result, Router};

async fn index(_: Request) -> Result<&'static str> {
    Ok("Hello, Viz!")
}

#[tokio::main]
async fn main() -> Result<()> {
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    let listener = TcpListener::bind(addr).await?;
    println!("listening on http://{addr}");

    let app = Router::new().get("/", index);

    if let Err(e) = serve(listener, app).await {
        println!("{e}");
    }

    Ok(())
}

More examples can be found here.

§Handler

A simple pattern async fn(Request) -> Result<Response> is used to handle requests in Viz.

§Simple handlers

async fn index(_: Request) -> Result<Response> {
    Ok(Response::text("Hello, World!"))
}

async fn about(_: Request) -> Result<&'static str> {
    Ok("About Me!")
}

async fn not_found(_: Request) -> Result<impl IntoResponse> {
    Ok("Not Found!")
}

§Implemented Handler trait

The types can implement the Handler trait to customize handlers.

#[derive(Clone)]
struct MyHandler {
    code: Arc<AtomicUsize>,
}

#[async_trait]
impl Handler<Request> for MyHandler {
    type Output = Result<Response>;

    async fn call(&self, req: Request) -> Self::Output  {
        let path = req.path();
        let method = req.method().clone();
        let code = self.code.fetch_add(1, Ordering::SeqCst);
        Ok(format!("code = {}, method = {}, path = {}", code, method, path).into_response())
    }
}

§With extractors

Supports handler with zero or one or more extractors.

Extractors must implement the FromRequest trait for extracting data from the Request.

When joining the routing system, it should first be converted to a handler using into_handler.

async fn show_todo(Params(id): Params<u64>) -> Result<impl IntoResponse> {
    Ok(format!("Hi, NO.{}", id))
}

let app = Router::new().route("/:id", get(show_todo.into_handler()));

§Why not supports handler with extractors by default?

Viz allows more flexibility in organizing your code.

async fn show_user(mut req: Request) -> Result<Response> {
    let Params(id)  = req.extract::<Params<u64>>().await?;
    Ok(format!("post {}", id).into_response())
}

async fn show_user_ext(Params(id): Params<u64>) -> Result<impl IntoResponse> {
    Ok(format!("Hi, NO.{}", id))
}

async fn show_user_wrap(req: Request) -> Result<impl IntoResponse> {
    // https://github.com/rust-lang/rust/issues/48919
    // show_user_ext.call(req).await
    FnExt::call(&show_user_ext, req).await
}

let app = Router::new()
    .get("/users/:id", show_user)
    .get("/users_wrap/:id", show_user_wrap)
    .get("/users_ext/:id", show_user_ext.into_handler());

§Support process macros?

Support, you can enable the macros feature, using #[handler].

But it’s still recommended to use into_handler for conversion.


#[handler]
async fn show_user(Params(id): Params<u64>) -> Result<impl IntoResponse> {
    Ok(format!("Hi, NO.{}", id))
}

async fn update_user(Params(id): Params<u64>) -> Result<impl IntoResponse> {
    Ok(format!("Updated, NO.{}", id))
}

let app = Router::new()
    .get("/users/:id", show_user)
    .patch("/users/:id", update_user.into_handler());

§Chaining and composing handlers

The HandlerExt is an extension trait for Handlers that provides a variety of convenient combinator functions.

Likes the FutureExt and StreamExt traits.

async fn index(_: Request) -> Result<Response> {
    Ok(Response::text("hyper"))
}

async fn before(req: Request) -> Result<Request> {
    if req.method() == Method::POST {
        Ok(req)
    } else {
        Err(StatusCode::METHOD_NOT_ALLOWED.into_error())
    }
}

async fn around<H>((req, handler): Next<Request, H>) -> Result<Response>
where
    H: Handler<Request, Output = Result<Response>>,
{
    // before ...
    let result = handler.call(req).await;
    // after ...
    result
}

async fn after(result: Result<Response>) -> Result<Response> {
    result.map(|mut res| {
        *res.status_mut() = StatusCode::NO_CONTENT;
        res
    })
}

let routing = Router::new()
    .get("/", index.before(before).around(around).after(after));

§Middleware

Viz’s middleware and handlers share a common Handler trait, so its very easy to implement and extend the middleware.

We can add middleware to a single handler, or to all handlers.

We can also use Transform trait for wrapping the inner handler during construction.

async fn index(_: Request) -> Result<Response> {
    Ok(StatusCode::OK.into_response())
}

async fn not_found(_: Request) -> Result<impl IntoResponse> {
    Ok(StatusCode::OK)
}

async fn show_user(Params(id): Params<u64>) -> Result<impl IntoResponse> {
    Ok(format!("post {}", id))
}

// middleware fn
async fn around<H>((req, handler): Next<Request, H>) -> Result<Response>
where
    H: Handler<Request, Output = Result<Response>>,
{
    // before ...
    let result = handler.call(req).await;
    // after ...
    result
}

// middleware struct
#[derive(Clone)]
struct MyMiddleware {}

#[async_trait]
impl<H> Handler<Next<Request, H>> for MyMiddleware
where
    H: Handler<Request>,
{
    type Output = H::Output;

    async fn call(&self, (i, h): Next<Request, H>) -> Self::Output {
        h.call(i).await
    }
}

// A configuration for Timeout Middleware
struct Timeout {
    delay: Duration,
}

impl Timeout {
    pub fn new(secs: u64) -> Self {
        Self { delay: Duration::from_secs(secs) }
    }
}

impl<H: Clone> Transform<H> for Timeout {
    type Output = TimeoutMiddleware<H>;

    fn transform(&self, h: H) -> Self::Output {
        TimeoutMiddleware(h, self.delay)
    }
}

// Timeout Middleware
#[derive(Clone)]
struct TimeoutMiddleware<H>(H, Duration);

#[async_trait]
impl<H> Handler<Request> for TimeoutMiddleware<H>
where
    H: Handler<Request>,
{
    type Output = H::Output;

    async fn call(&self, req: Request) -> Self::Output {
        self.0.call(req).await
    }
}

let app = Router::new()
    .get("/", index
        // handler level
        .around(around)
        .around(MyMiddleware {})
        .with(Timeout::new(1))
    )
    .route("/users/:id", get(
        show_user
            .into_handler()
            .map_into_response()
            // handler level
            .around(around)
            .with(Timeout::new(0))
        )
        .post(
            (|_| async { Ok(Response::text("update")) })
            // handler level
            .around(around)
            .with(Timeout::new(0))
        )
        // route level
        .with_handler(MyMiddleware {})
        .with(Timeout::new(2))
    )
    .get("/*", not_found
        .map_into_response()
        // handler level
        .around(around)
        .around(MyMiddleware {})
    )
    // router level
    .with_handler(around)
    .with_handler(MyMiddleware {})
    .with(Timeout::new(4));

§Extractors

Extracts data from the Request.

struct Counter(u16);

impl FromRequest for Counter {
    type Error = Infallible;
    async fn extract(req: &mut Request) -> Result<Self, Self::Error> {
        let c = get_query_param(req.query_string());
        Ok(Counter(c))
    }
}

fn get_query_param(query: Option<&str>) -> u16 {
   let query = query.unwrap_or("");
   let q = if let Some(pos) = query.find('q') {
       query.split_at(pos + 2).1.parse().unwrap_or(1)
   } else {
       1
   };
   cmp::min(500, cmp::max(1, q))
}

§Routing

The Viz router recognizes URLs and dispatches them to a handler.

§Simple routes

async fn index(_: Request) -> Result<Response> {
    Ok(().into_response())
}

let root = Router::new()
  .get("/", index)
  .route("/about", get(|_| async { Ok("about") }));

let search = Router::new()
  .route("/", Route::new().get(|_| async { Ok("search") }));

§CRUD, Verbs

Adds routes with the HTTP method.

async fn index_todos(_: Request) -> Result<impl IntoResponse> {
    Ok(())
}

async fn create_todo(_: Request) -> Result<&'static str> {
    Ok("created")
}

async fn new_todo(_: Request) -> Result<Response> {
    Ok(Response::html(r#"
        <form method="post" action="/">
            <input name="todo" />
            <button type="submit">Create</button>
        </form>
    "#))
}

async fn show_todo(mut req: Request) -> Result<Response> {
    let Params(id): Params<u64> = req.extract().await?;
    Ok(Response::text(format!("todo's id is {}", id)))
}

async fn update_todo(_: Request) -> Result<()> {
    Ok(())
}

async fn destroy_todo(_: Request) -> Result<()> {
    Ok(())
}

async fn edit_todo(_: Request) -> Result<()> {
    Ok(())
}

let todos = Router::new()
  .route("/", get(index_todos).post(create_todo))
  .post("/new", new_todo)
  .route("/:id", get(show_todo).patch(update_todo).delete(destroy_todo))
  .get("/:id/edit", edit_todo);

§Resources

// GET `/search`
async fn search_users(_: Request) -> Result<Response> {
    Ok(Response::json::<Vec<u64>>(vec![])?)
}

// GET `/`
async fn index_users(_: Request) -> Result<Response> {
    Ok(Response::json::<Vec<u64>>(vec![])?)
}

// GET `/new`
async fn new_user(_: Request) -> Result<&'static str> {
    Ok("User Form")
}

// POST `/`
async fn create_user(_: Request) -> Result<&'static str> {
    Ok("Created User")
}

// GET `/user_id`
async fn show_user(_: Request) -> Result<&'static str> {
    Ok("User ID 007")
}

// GET `/user_id/edit`
async fn edit_user(_: Request) -> Result<&'static str> {
    Ok("Edit User Form")
}

// PUT `/user_id`
async fn update_user(_: Request) -> Result<&'static str> {
    Ok("Updated User")
}

// DELETE `/user_id`
async fn delete_user(_: Request) -> Result<&'static str> {
    Ok("Deleted User")
}

let users = Resources::default()
  .named("user")
  .route("/search", get(search_users))
  .index(index_users)
  .new(new_user)
  .create(create_user)
  .show(show_user)
  .edit(edit_user)
  .update(update_user)
  .destroy(delete_user);

§Nested

async fn not_found(_: Request) -> Result<impl IntoResponse> {
    Ok(StatusCode::NOT_FOUND)
}

let app = Router::new()
  .nest("/", root)
  .nest("/search", search)
  .nest("/todos", todos.clone())
  .nest("/users", users.nest("todos", todos))
  .route("/*", any(not_found));

Modules§

future
Asynchronous values.
handler
Traits and types for handling an HTTP.
handlershandlers
A collection of handlers for Viz.
header
HTTP header types
headers
Typed HTTP Headers
middleware
Built-in Middleware.
tlsnative-tls or rustls
TLS A TLS listener wrapper.
types
Built-in Extractors types and traits.

Structs§

BoxHandler
A Clone + Send boxed Handler.
Bytes
A cheaply cloneable and sliceable chunk of contiguous memory.
BytesMut
A unique reference to a contiguous slice of memory.
Incoming
A stream of Bytes, used when receiving bodies from the network.
Io
A wrapper that implements Tokio’s IO traits for an inner type that implements hyper’s IO traits, or vice versa (implements hyper’s IO traits for a type that implements Tokio’s IO traits).
Method
The Request Method (VERB)
Path
Matched route path infomation.
PathTree
A path tree.
Resources
A resourceful route provides a mapping between HTTP verbs and URLs to handlers.
Responder
Handles the HTTP Request and retures the HTTP Response.
Route
A collection of verb-handler pair.
Router
A routes collection.
Server
A listening HTTP server that accepts connections.
StatusCode
An HTTP status code (status-code in RFC 9110 et al.).
Tree
Store all final routes.

Enums§

Body
A body for HTTP Request and HTTP Response.
BodyState
A body state.
Error
Represents errors that can occur handling application.

Traits§

FnExt
A handler with extractors.
FromRequest
An interface for extracting data from the HTTP Request.
Future
A future represents an asynchronous computation obtained by use of async.
Handler
A simplified asynchronous interface for handling input and output.
HandlerExt
The HandlerExt trait, which provides adapters for chaining and composing handlers.
HttpBody
Trait representing a streaming body of a Request or Response.
IntoHandler
The trait implemented by types that can be converted to a Handler.
IntoResponse
Trait implemented by types that can be converted to an HTTP Response.
Listener
A trait for a listener: TcpListener and UnixListener.
RequestExt
The Request Extension.
RequestLimitsExt
The Request Extension with a limited body.
ResponseExt
The Response Extension.
Transform
Then Transform trait defines the interface of a handler factory that wraps inner handler to a Handler during construction.

Functions§

any
Creates a route with a handler and any HTTP verbs.
connect
Creates a route with a handler and HTTP CONNECT verb pair.
delete
Creates a route with a handler and HTTP DELETE verb pair.
get
Creates a route with a handler and HTTP GET verb pair.
head
Creates a route with a handler and HTTP HEAD verb pair.
on
Creates a route with a handler and HTTP verb pair.
options
Creates a route with a handler and HTTP OPTIONS verb pair.
patch
Creates a route with a handler and HTTP PATCH verb pair.
post
Creates a route with a handler and HTTP POST verb pair.
put
Creates a route with a handler and HTTP PUT verb pair.
serve
Starts a server and serves the connections.
trace
Creates a route with a handler and HTTP TRACE verb pair.

Type Aliases§

BoxError
An owned dynamically typed StdError.
Next
Represents a middleware parameter, which is a tuple that includes Requset and BoxHandler.
Request
Represents an HTTP Request.
Response
Represents an HTTP Response.
Result
Represents either success (Ok) or failure (Err).

Attribute Macros§

async_trait
handlermacros
Transforms extract-handler to a Handler instance.

Derive Macros§

ThisError