Coder Social home page Coder Social logo

httprouter-rs's Introduction

HttpRouter

Documentation Version License Actions

HttpRouter is a lightweight high performance HTTP request router.

This router supports variables in the routing pattern and matches against the request method. It also scales very well.

The router is optimized for high performance and a small memory footprint. It scales well even with very long paths and a large number of routes. A compressing dynamic trie (radix tree) structure is used for efficient matching. Internally, it uses the matchit package.

Features

Only explicit matches: With other routers, a requested URL path could match multiple patterns. Therefore they have some awkward pattern priority rules, like longest match or first registered, first matched. By design of this router, a request can only match exactly one or no route. As a result, there are also no unintended matches, which makes it great for SEO and improves the user experience.

Path auto-correction: Besides detecting the missing or additional trailing slash at no extra cost, the router can also fix wrong cases and remove superfluous path elements (like ../ or //). Is CAPTAIN CAPS LOCK one of your users? HttpRouter can help him by making a case-insensitive look-up and redirecting him to the correct URL.

Parameters in your routing pattern: Stop parsing the requested URL path, just give the path segment a name and the router delivers the dynamic value to you. Because of the design of the router, path parameters are very cheap.

High Performance: HttpRouter relies on a tree structure which makes heavy use of common prefixes, it is basically a radix tree. This makes lookups extremely fast. Internally, it uses the matchit package.

Of course you can also set custom NotFound and MethodNotAllowed handlers , serve static files, and automatically respond to OPTIONS requests

Usage

Here is a simple example:

use httprouter::{Router, Params, handler_fn};
use hyper::{Request, Response, Body, Error};

async fn index(_: Request<Body>) -> Result<Response<Body>, Error> {
    Ok(Response::new("Hello, World!".into()))
}

async fn hello(req: Request<Body>) -> Result<Response<Body>, Error> {
    let params = req.extensions().get::<Params>().unwrap();
    Ok(Response::new(format!("Hello, {}", params.get("user").unwrap()).into()))
}

#[tokio::main]
async fn main() {
    let router = Router::default()
        .get("/", handler_fn(index))
        .get("/hello/:user", handler_fn(hello));

    hyper::Server::bind(&([127, 0, 0, 1], 3000).into())
        .serve(router.into_service())
        .await;
}

Named parameters

As you can see, :user is a named parameter. The values are accessible via req.extensions().get::<Params>().

Named parameters only match a single path segment:

Pattern: /user/:user

 /user/gordon              match
 /user/you                 match
 /user/gordon/profile      no match
 /user/                    no match

Note: Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns /user/new and /user/:user for the same request method at the same time. The routing of different request methods is independent from each other.

Catch-All parameters

The second type are catch-all parameters and have the form *name. Like the name suggests, they match everything. Therefore they must always be at the end of the pattern:

Pattern: /src/*filepath

 /src/                     match
 /src/somefile.go          match
 /src/subdir/somefile.go   match

Automatic OPTIONS responses and CORS

One might wish to modify automatic responses to OPTIONS requests, e.g. to support CORS preflight requests or to set other headers. This can be achieved using the Router::global_options handler:

use httprouter::{Router, handler_fn};
use hyper::{Request, Response, Body, Error};

async fn cors(_: Request<Body>) -> Result<Response<Body>, Error> {
    let res = Response::builder()
        .header("Access-Control-Allow-Methods", "Allow")
        .header("Access-Control-Allow-Origin", "*")
        .body(Body::empty())
        .unwrap();
    Ok(res)
}

fn main() {
    let router = Router::default().global_options(handler_fn(cors));
}

Multi-domain / Sub-domains

Here is a quick example: Does your server serve multiple domains / hosts? You want to use sub-domains? Define a router per host!

use httprouter::{Router, BoxError};
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response};
use std::collections::HashMap;
use std::convert::Infallible;
use std::sync::Arc;

#[derive(Default)]
pub struct HostSwitch(HashMap<String, Router>);

impl HostSwitch {
    async fn serve(&self, req: Request<Body>) -> Result<Response<Body>, BoxError> {
        let forbidden = || Response::builder()
            .status(403)
            .body(Body::empty())
            .unwrap();
        match req.headers().get("host") {
            Some(host) => match self.0.get(host.to_str().unwrap()) {
                Some(router) => router.serve(req).await,
                None => Ok(forbidden()),
            },
            None => Ok(forbidden()),
        }
    }
}

#[tokio::main]
async fn main() {
    let mut host_switch = HostSwitch::default();
    host_switch.0.insert("example.com:12345".into(), Router::default());

    let host_switch = Arc::new(host_switch);
    
    let make_svc = make_service_fn(move |_| {
        let host_switch = host_switch.clone();
        async move {
            Ok::<_, Infallible>(service_fn(move |req| {
                let host_switch = host_switch.clone();
                async move { host_switch.serve(req).await }
            }))
        }
    });

    hyper::Server::bind(&([127, 0, 0, 1], 3000).into())
        .serve(make_svc)
        .await;
}

Not Found Handler

NOTE: It might be required to set Router::method_not_allowed to None to avoid problems.

You can use another handler, to handle requests which could not be matched by this router by using the Router::not_found handler.

The not_found handler can for example be used to return a 404 page:

use httprouter::{Router, handler_fn};
use hyper::{Request, Response, Body, Error};

async fn not_found(req: Request<Body>) -> Result<Response<Body>, Error> {
    let res = Response::builder()
	    .status(404)
	    .body(Body::empty())
	    .unwrap();
    Ok(res)
}

fn main() {
    let router = Router::default().not_found(handler_fn(not_found));
}

Static files

You can use the router to serve pages from a static file directory:

// TODO

httprouter-rs's People

Contributors

ibraheemdev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

httprouter-rs's Issues

Percent-encoded paths are not handled correctly

Here's two failing examples:

use http::Method;
use httprouter::Router;

fn main() {
    let mut router = Router::default();
    router.get("/foo%2Fbar", "Hello");
    router.get("/foo bar", "Hello");

    assert_eq!(
        router.lookup(&Method::GET, "/foo%2fbar").unwrap().value,
        &"Hello"
    );

    assert_eq!(
        router.lookup(&Method::GET, "/foo%20bar").unwrap().value,
        &"Hello"
    );
}

Remove `Sync` bounds

Currently all route handlers needs to be Send, Sync and 'static, the default hyper Executor only requires Send and 'static bounds (see this line).

The Sync bound is currently needed because RouterService holds an Arc<Router> which only implements Send if the underlying type is both Send AND Sync.

A workaround would be to wrap the router in a Mutex: Arc<Mutex<Router>>. Since Mutex implements Send and Sync for all types that are Send this should removes the need for all Sync bounds.

However this would prevent the user from wrapping a Router in an Arc themself:

// `router` won't be `Send` because of `Arc` `Sync` requirements
let router = Arc::new(Router::default());

let make_svc = make_service_fn(move |_| {
     let router = router.clone();
     async move {
         Ok::<_, Infallible>(service_fn(move |req: Request<Body>| {
             let router = router.clone();
             async move { router.serve(req).await }
         }))
     }
 });

 let server = Server::bind(&([127, 0, 0, 1], 3000).into())
     .serve(make_svc)
     .await;

An alternative would be to wrap all the inner fields of Router in a struct, wrapped in a Mutex:

struct Router {
    inner: Mutex<RouterInner>
}

struct RouterInner {
    /* -- snip --*/
}

There may be other solutions, but I'm not aware of them ATM.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.