Route

Volo-HTTP route function

Route Registration

Route handlers can be created using get, post and other functions.

The corresponding functions can be imported in the volo_http::route package, e.g, get via volo_http::route::get.

First, we need a simple handler: volo_http::route::get.

async fn foo_handler() -> &'static str {
    "Hello, World!\n"
}

Each route requires a relative path and a method. Let’s take the GET method for routing to the “/foo” path as an example,

which can be created by using the route method after creating a Router in the following way.

use volo_http::server::route::{get, Router};

pub fn test_router() -> Router {
    Router::new().route("/foo", get(foo_handler))
}

You can chain calls to MethodRouter from get to set handlers for other methods.

use volo_http::server::route::{get, Router};

pub fn test_router() -> Router {
    Router::new()
        .route("/foo", get(foo_handoer).post(foo_handler))
}

Routing rules can also be created by chaining calls to Router:

use volo_http::server::route::{get, Router};

pub fn test_router() -> Router {
    Router::new()
        .route("/foo", get(foo_handler))
        .route("/bar", post(bar_handler))
}

Once created, the route can be merged into the main route:

use std::net::SocketAddr;
use std::time::Duration;
use volo::net::Address;
use volo_http::{Router, Server};

#[volo::main]
async fn main() {
    let app = Router::new()
        .merge(example_router())
        .merge(test_router())
        .layer(TimeoutLayer::new(Duration::from_secs(1), timeout_handler));

    let addr = "[::]:8080".parse::<SocketAddr>().unwarp();
    let addr = Address::from(addr);

    Server::new(app).run(addr).await.unwarp();
}

Routing methods

Use a method such as use volo_http::server::route::get to import a route as a GET.

Currently supported routing methods are:

  • options
  • get
  • post
  • put
  • delete
  • head
  • trace
  • connect
  • trace

Route Types

Volo-HTTP supports rich route types for complex route matching, including static routes, dynamic routes (named parameter, wildcard).

Priority of routes: static routes > named parameter routes > wildcard routes.

Static route

use volo_http::server::route::{get, Router};

async fn index_handler() -> &'static str {
    "Hello, World"
}

async fn index_router() -> Router {
    Router::new().route("/", get(index));
}

Named Parameter Route

Volo-HTTP supports routing with named parameters like {id}, and the named parameters only match a single path segment.

If you set up a /user/{id} route, the match will be as follows.

Route Matched Param Value
/user/100 Yes 100
/user/101/profile No -
/user/ No -

Volo-HTTP also supports multiple named parameters, e.g. /{platform}/user/{id}.

Example:

use volo::FastStr;
use volo_http::server::{
    param::PathParamsMap,
    route::{get, Router},
};

async fn param_handler(map: PathParamsMap) -> FastStr {
    // Note: The `unwarp` method is not recommended for production environments.
    // It is used here for tutorial purposes only.
    map.get("id").unwarp().clone()
}

async fn param_router() -> Router {
    Router::new().route("/user/{id}", get(param_handler))
}

Wildcard Route

Volo-HTTP supports the use of *path as a wildcard parameter for routes, and the wildcard parameter matches everything.

**Note: The wildcard parameter must be placed at the end of the route. **Note: Wildcards should be placed at the end of the route.

If we set up a /src/{*path} route, it will match as follows:

Route Matched Param Value
/src/ Yes -
/src/somefile.rs No somefile.rs
/src/subdir/somefile.rs No subdir/somefile.rs

Example:

use volo::FastStr;
use volo_http::server::{
    param::PathParamsMap,
    route::{get, Router},
};

async fn param_handler(map: PathParamsMap) -> FastStr {
    // Note: The `unwarp` method is not recommended for production environments.
    // It is used here for tutorial purposes only.
    map.get("path").unwarp().clone()
}

async fn param_router() -> Router {
    Router::new().route("/src/{*path}", get(param_handler))
}

Routing parameter values extraction

  • PathParamsMap

    Provide a routing parameter AHashmap

    use volo_http::param::PathParamsMap;
    
    async fn param_handler(map: PathParamsMap) -> FastStr {
        // Note: The `unwarp` method is not recommended for production environments.
        // It is used here for tutorial purposes only.
        map.get("id").unwarp().clone()
    }
    
  • PathParams

    User-definable routing parameter values using pattern matching features

    use volo_http::param::PathParams;
    
    async fn param_handler(PathParams(id): PathParams<String>) -> String {
        id
    }
    

Route Group

Volo-HTTP provides the ability to route nests, which are used to support route grouping.

impl<B, E> Router<B, E> {
    /*...*/
    pub fn nest<U>(self, uri: U, router: Router<B, E>) -> Self
    where
        U: AsRef<str>,
    {
        self.nest_route(uri.as_ref().to_owned(), Route::new(router))
    }
}

We can use the given url prefix path as the route prefix for a given route, and then merge it into the current route

use volo_http::server::{
    param::PathParams,
    route::{get, Router},
};

async fn hello_world() -> &'static str {
    "Hello, World"
}

async fn get_tid(PathParams(tid): PathParams<String>) -> String {
    tid
}

async fn get_uid_and_tid(PathParams((uid, tid)): PathParams<(String, String)>) -> String {
    format!("uid: {uid}, tid: {tid}")
}

async fn user_router() -> Router {
    Router::new()
        .route("/name", get(hello_world))
        .route("/post/{tid}", get(get_uid_and_tid));
}

async fn post_router() -> Router {
    Router::new()
        .route("/name", get(hello_world))
        .route("/{tid}", get(get_tid));
}

async fn router() -> Router {
    Router::new()
        .nest("/user/{uid}", user_router())
        .nest("/post", post_router())
    // The routing paths here are as follows.
    // /user/{uid}/name
    // /user/{uid}/post/{tid}
    // /post/name
    // /post/{tid}
}

Fallback

Volo-HTTP provides a fallback function to handle request url or method mismatches, default fallback will return status code 404 Not Found.

Note:

  • Since there is no way to determine which router fallback to override in a route in a call to the merge method, only one router fallback can be set, or else a panic will be generated in the call to the merge method;

  • The nest method is called without overriding the fallback.

url

Example:

async fn index_handler() -> &'static str {
    "Hello, World"
}

async fn fallback_handler() -> (http::StatusCode, &'static str) {
  (http::StatusCode::NOT_FOUND, "404 Not Found")
}

async fn router() -> Router {
    Router::new()
        .route("/", get(index_handler))
        .fallback(fallback_handler)
}

method

async fn index_handler() -> &'static str {
    "Hello, World"
}

async fn fallback_handler() -> (http::StatusCode, &'static str) {
  (http::StatusCode::METHOD_NOT_ALLOWED, "method not matched")
    
}

async fn router() -> Router {
    Router::new()
        .route("/", get(index_handler).fallback(fallback_handler))
}

Using Service as a Route

Routes can be implemented using a traditional Service, and the handler approach mentioned above will also work as a Service, but you need to create a route for the GET method via get_service:

use std::convert::Infallible;

use volo_http::{
    context::ServerContext,
    request::ServerRequest,
    response::ServerResponse,
    server::{
        route::{get_service, Router},
        IntoResponse,
    },
};
use motore::Service;

#[derive(Clone)]
pub struct JsonGetService;

impl Service<ServerContext, ServerRequest> for JsonGetService {
    type Response = ServerResponse;
    type Error = Infallible;

    async fn call(
        &self,
        _cx: &mut ServerContext,
        _req: ServerRequest,
    ) -> Result<Self::Response, Self::Error> {
        Ok(json_get().await.into_response())
    }
}

pub fn json_test_router() -> Router {
    Router::new()
        /* ...... */
        .route(
            "/json/get_srv",
            get_service(JsonGetService),
        )
}

For this simpler Service, it is also possible to implement it directly using a function via service_fn without defining a structure:

use std::convert::Infallible;

use volo_http::{
    context::ServerContext,
    json::Json,
    request::ServerRequest,
    response::ServerResponse,
    server::{
        route::{post_service, Router},
        IntoResponse,
    },
};
use motore::{service::service_fn, Service};

async fn json_post_srv(
    cx: &mut ServerContext,
    req: ServerRequest,
) -> Result<ServerResponse, Infallible> {
    let (parts, body) = req.into_parts();
    let data = match Json::<Person>::from_request(cx, parts, body).await {
        Ok(data) => data,
        Err(_) => {
            return Ok("Invalid data!".into_response());
        }
    };
    Ok(json_post(Some(data)).await.into_response())
}

pub fn json_test_router() -> Router {
    Router::new()
        /* ...... */
        .route(
            "/json/post_srv",
            post_service(service_fn(json_post_srv)),
        )
}

The final run is expected to be consistent with json_get and json_post.


Last modified November 14, 2024 : Feat: Documentation for Monolake (#1165) (2667d6a)