[][src]Crate routerify

The Routerify provides a lightweight and modular router implementation with middleware support for the Rust HTTP library hyper.rs.

There are a lot of web server frameworks for Rust applications out there and hyper.rs being comparably very fast and ready for production use is one of them, and it provides only low level APIs. It doesn't provide any complex routing feature. So, Routerify extends the hyper.rs library by providing that missing feature without compromising any performance.

The Routerify offers the following features:

  • 📡 Allows defining complex routing logic.

  • 🔨 Provides middleware support.

  • 🌀 Supports Route Parameters.

  • 🚀 Fast as it's using RegexSet to match routes.

  • 🍺 It supports any response body type as long as it implements the HttpBody trait.

  • ❗ Provides a flexible error handling strategy.

  • 🍗 Exhaustive examples and well documented.

To generate a quick server app using Routerify and hyper.rs, please check out hyper-routerify-server-template.

Benchmarks

FrameworkLanguageRequests/sec
hyper v0.13Rust 1.43.0112,557
routerify v1.1 with hyper v0.13Rust 1.43.0112,320
gotham v0.4.0Rust 1.43.0100,097
actix-web v2Rust 1.43.096,397
warp v0.2Rust 1.43.081,912
go-httprouter, branch masterGo 1.13.774,958
Rocket, branch asyncRust 1.43.02,041 ?

For more info, please visit Benchmarks.

Basic Example

A simple example using Routerify with hyper.rs would look like the following:

use hyper::{Body, Request, Response, Server};
// Import the routerify prelude traits.
use routerify::prelude::*;
use routerify::{Middleware, Router, RouterService};
use std::{convert::Infallible, net::SocketAddr};

// A handler for "/:name" page.
async fn home_handler(req: Request<Body>) -> Result<Response<Body>, Infallible> {
    let name = req.param("name").unwrap();
    Ok(Response::new(Body::from(format!("Hello {}", name))))
}

// A handler for "/about" page.
async fn about_handler(_: Request<Body>) -> Result<Response<Body>, Infallible> {
    Ok(Response::new(Body::from("About page")))
}

// A middleware which logs an http request.
async fn logger(req: Request<Body>) -> Result<Request<Body>, Infallible> {
    println!("{} {} {}", req.remote_addr(), req.method(), req.uri().path());
    Ok(req)
}

// Create a `Router<Body, Infallible>` for response body type `hyper::Body` and for handler error type `Infallible`.
fn router() -> Router<Body, Infallible> {
    // Create a router and specify the logger middleware and the handlers.
    // Here, "Middleware::pre" means we're adding a pre middleware which will be executed
    // before any route handlers.
    Router::builder()
        .middleware(Middleware::pre(logger))
        .get("/:name", home_handler)
        .get("/about", about_handler)
        .build()
        .unwrap()
}

#[tokio::main]
async fn main() {
    let router = router();

    // Create a Service from the router above to handle incoming requests.
    let service = RouterService::new(router).unwrap();

    // The address on which the server will be listening.
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

    // Create a server by passing the created service to `.serve` method.
    let server = Server::bind(&addr).serve(service);

    println!("App is running on: {}", addr);
    if let Err(err) = server.await {
        eprintln!("Server error: {}", err);
   }
}

Routing

Route Handlers

A handler could be a function or a closure to handle a request at a specified route path.

Here is a handler with a function:

use routerify::Router;
use hyper::{Response, Request, Body};
use std::convert::Infallible;

// A handler for "/about" page.
async fn about_handler(_: Request<Body>) -> Result<Response<Body>, Infallible> {
    Ok(Response::new(Body::from("About page")))
}

let router = Router::builder()
    .get("/about", about_handler)
    .build()
    .unwrap();

Here is a handler with closure function:

use routerify::Router;
use hyper::{Response, Body};

let router = Router::builder()
    .get("/about", |req| async move { Ok(Response::new(Body::from("About page"))) })
    .build()
    .unwrap();

Route Paths

Route paths, in combination with a request method, define the endpoints at which requests can be made. Route paths can be strings or strings with glob pattern *.

Here are some examples:

This route path will match with exactly "/about":

use routerify::Router;
use hyper::{Response, Body};

let router = Router::builder()
    .get("/about", |req| async move { Ok(Response::new(Body::from("About page"))) })
    .build()
    .unwrap();

A route path using the glob * pattern:

use routerify::Router;
use hyper::{Response, Body};

let router = Router::builder()
    .get("/users/*", |req| async move { Ok(Response::new(Body::from("It will match /users/, /users/any_path"))) })
    .build()
    .unwrap();

Handle 404 Pages

Here is an example to handle 404 pages.

use routerify::Router;
use hyper::{Response, Body, StatusCode};

let router = Router::builder()
    .get("/users", |req| async move { Ok(Response::new(Body::from("User List"))) })
    // It fallbacks to the following route for any non-existent routes.
    .any(|_req| async move {
        Ok(
            Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(Body::from("NOT FOUND"))
            .unwrap()
        )
    })
    .build()
    .unwrap();

Route Parameters

Route parameters are named URL segments that are used to capture the values specified at their position in the URL. The captured values can be accessed by req.params and re.param methods using the name of the route parameter specified in the path.

Route path: /users/:userName/books/:bookName
Request URL: http://localhost:3000/users/alice/books/HarryPotter
req.params() returns a hashmap: { "userName": "alice", "bookName": "HarryPotter" }

To define routes with route parameters, simply specify the route parameters in the path of the route as shown below.

use routerify::Router;
// Add routerify prelude traits.
use routerify::prelude::*;
use hyper::{Response, Body};

let router = Router::builder()
    .get("/users/:userName/books/:bookName", |req| async move {
        let user_name = req.param("userName").unwrap();
        let book_name = req.param("bookName").unwrap();

        Ok(Response::new(Body::from(format!("Username: {}, Book Name: {}", user_name, book_name))))
     })
     .build()
     .unwrap();

Scoping/Mounting Router

The routerify::Router is a modular, lightweight and mountable router component. A router can be scoped in or mount to a different router.

Here is a simple example which creates a Router and it mounts that router at /api path with .scope() method:

use routerify::Router;
use routerify::prelude::*;
use hyper::{Response, Body};
use std::convert::Infallible;

fn api_router() -> Router<Body, Infallible> {
    Router::builder()
        .get("/books", |req| async move { Ok(Response::new(Body::from("List of books"))) })
        .get("/books/:bookId", |req| async move {
            Ok(Response::new(Body::from(format!("Show book: {}", req.param("bookId").unwrap()))))
         })
        .build()
        .unwrap()
}

let router = Router::builder()
     // Mounts the API router at "/api" path .
     .scope("/api", api_router())
     .build()
     .unwrap();

Now, the app can handle requests to /api/books as well as to /api/books/:bookId.

Middleware

The Routerify also supports Middleware functionality. If you are unfamiliar with Middleware, in short, here a middlewar is a function (or could be a closure function) which access the req and res object and does some changes to them and passes the transformed request and response object to the other middlewares and the actual route handler to process it.

A Middleware function can do the following tasks:

  • Execute any code.
  • Transform the request and the response object.

Here, the Routerify categorizes the middlewares into two different types:

Pre Middleware

The pre Middlewares will be executed before any route handlers and it will access the req object and it can also do some changes to the request object if required.

Here is an example of a pre middleware:

use routerify::{Router, Middleware};
use hyper::{Request, Body};
use std::convert::Infallible;

// The handler for a pre middleware.
// It accepts a `req` and it transforms the `req` and passes it to the next middlewares.
async fn my_pre_middleware_handler(req: Request<Body>) -> Result<Request<Body>, Infallible> {
    // Do some changes if required.
    let transformed_req = req;

    // Then return the transformed request object to be consumed by the other middlewares
    // and the route handlers.
    Ok(transformed_req)
}

let router = Router::builder()
     // Create a pre middleware instance by `Middleware::pre` method
     // and attach it.
     .middleware(Middleware::pre(my_pre_middleware_handler))
     // A middleware can also be attached on a specific path as shown below.
     .middleware(Middleware::pre_with_path("/my-path/log", my_pre_middleware_handler).unwrap())
     .build()
     .unwrap();

Here is a pre middleware which logs the incoming requests:

use routerify::{Router, Middleware};
use routerify::prelude::*;
use hyper::{Request, Body};
use std::convert::Infallible;

async fn logger_middleware_handler(req: Request<Body>) -> Result<Request<Body>, Infallible> {
    println!("{} {} {}", req.remote_addr(), req.method(), req.uri().path());
    Ok(req)
}

let router = Router::builder()
     .middleware(Middleware::pre(logger_middleware_handler))
     .build()
     .unwrap();

Post Middleware

The post Middlewares will be executed after all the route handlers process the request and generates a response and it will access that response object and the request info(optional) and it can also do some changes to the response if required.

Here is an example of a post middleware:

use routerify::{Router, Middleware};
use hyper::{Response, Body};
use std::convert::Infallible;

// The handler for a post middleware.
// It accepts a `res` and it transforms the `res` and passes it to the next middlewares.
async fn my_post_middleware_handler(res: Response<Body>) -> Result<Response<Body>, Infallible> {
    // Do some changes if required.
    let transformed_res = res;

    // Then return the transformed response object to be consumed by the other middlewares.
    Ok(transformed_res)
}

let router = Router::builder()
     // Create a post middleware instance by `Middleware::post` method
     // and attach it.
     .middleware(Middleware::post(my_post_middleware_handler))
     // A middleware can also be attached on a specific path as shown below.
     .middleware(Middleware::post_with_path("/my-path/log", my_post_middleware_handler).unwrap())
     .build()
     .unwrap();

Here is a post middleware which adds a header to the response object:

use routerify::{Router, Middleware};
use routerify::prelude::*;
use hyper::{Response, Body, header::HeaderValue};
use std::convert::Infallible;

async fn my_post_middleware_handler(mut res: Response<Body>) -> Result<Response<Body>, Infallible> {
    // Add a header to response object.
    res.headers_mut().insert("x-my-custom-header", HeaderValue::from_static("my-value"));

    Ok(res)
}

let router = Router::builder()
     .middleware(Middleware::post(my_post_middleware_handler))
     .build()
     .unwrap();

Post Middleware with Request Info

Sometimes, the post middleware requires the request informations e.g. headers, method, uri etc to generate a new response. As an example, it could be used to manage sessions. To register this kind of post middleware, you have to use Middleware::post_with_info method as follows:

use routerify::{Router, Middleware, RequestInfo};
use hyper::{Response, Body};
use std::convert::Infallible;

// The handler for a post middleware which requires request info.
// It accepts `res` and `req_info` and it transforms the `res` and passes it to the next middlewares.
async fn post_middleware_with_info_handler(res: Response<Body>, req_info: RequestInfo) -> Result<Response<Body>, Infallible> {
    let transformed_res = res;

    // Do some response transformation based on the request headers, method etc.
    let _headers = req_info.headers();

    // Then return the transformed response object to be consumed by the other middlewares.
    Ok(transformed_res)
}

let router = Router::builder()
     // Create a post middleware instance by `Middleware::post_with_info` method
     // and attach it.
     .middleware(Middleware::post_with_info(post_middleware_with_info_handler))
     // This middleware can also be attached on a specific path as shown below.
     .middleware(Middleware::post_with_info_with_path("/my-path", post_middleware_with_info_handler).unwrap())
     .build()
     .unwrap();

The built-in Middlewars

Here is a list of some middlewares which are published in different crates:

Error Handling

Any route or middleware could go wrong and throws an error. The Routerify tries to add a default error handler in some cases. But, it also allow to attach a custom error handler. The error handler generates a response based on the error and the request info(optional).

Here is an basic example:

use routerify::{Router, Middleware};
use routerify::prelude::*;
use hyper::{Response, Body, StatusCode};

// The error handler will accept the thrown error in routerify::Error type and
// it will have to generate a response based on the error.
async fn error_handler(err: routerify::Error) -> Response<Body> {
    Response::builder()
      .status(StatusCode::INTERNAL_SERVER_ERROR)
      .body(Body::from("Something went wrong"))
      .unwrap()
}

let router = Router::builder()
     .get("/users", |req| async move { Ok(Response::new(Body::from("It might raise an error"))) })
     // Here attach the custom error handler defined above.
     .err_handler(error_handler)
     .build()
     .unwrap();

Error Handling with Request Info

Sometimes, it's needed to to generate response on error based on the request headers, method, uri etc. The Routerify also provides a method err_handler_with_info to register this kind of error handler as follows:

use routerify::{Router, Middleware, RequestInfo};
use routerify::prelude::*;
use hyper::{Response, Body, StatusCode};

// The error handler will accept the thrown error and the request info and
// it will generate a response.
async fn error_handler(err: routerify::Error, req_info: RequestInfo) -> Response<Body> {
    // Now generate response based on the `err` and the `req_info`.
    Response::builder()
      .status(StatusCode::INTERNAL_SERVER_ERROR)
      .body(Body::from("Something went wrong"))
      .unwrap()
}

let router = Router::builder()
     .get("/users", |req| async move { Ok(Response::new(Body::from("It might raise an error"))) })
     // Now register this error handler.
     .err_handler_with_info(error_handler)
     .build()
     .unwrap();

Modules

ext
prelude

Structs

Error

The error type used by the Routerify library.

PostMiddleware

The post middleware type. Refer to Post Middleware for more info.

PreMiddleware

The pre middleware type. Refer to Pre Middleware for more info.

RequestInfo

Represents some information for the incoming request.

Route

Represents a single route.

RouteParams

Represents a map of the route parameters using the name of the parameter specified in the path as their respective keys.

Router

Represents a modular, lightweight and mountable router type.

RouterBuilder

Builder for the Router type.

RouterService

A Service to process incoming requests.

Enums

Middleware

Enum type for all the middleware types. Please refer to the Middleware for more info.

Type Definitions

Result

A Result type often returned from methods that can have routerify errors.