squall-router

Crates.iosquall-router
lib.rssquall-router
version0.2.2
sourcesrc
created_at2021-12-18 22:58:56.467918
updated_at2022-01-15 11:42:34.391867
descriptionHTTP router with path parameters extraction
homepage
repositoryhttps://github.com/mtag-dev/rs-squall-router
max_upload_size
id500162
size116,491
Stanislav Dubrovskyi (stas-dubrovskyi)

documentation

README

High performance HTTP router

Initially created as routing subsystem for Python's Squall API Framework.

Designed to mitigate the overhead of large-scale routing tables.

Squall router avoids massive Regex processing and performs validation only in those places where it is strictly necessary

The primary concept is finding relevant handlers using the HashMap-based database. Then perform computing only suitable entities and not the entire table. Regex matching is performed only for those fields which have defined data validators.

It is suitable for:

  • API Gateways

  • API services where routing cache not an option because of a lot of different parameters values

  • Just to ensure that routing is not a point of performance drop after adding a batch of new endpoints

Performance

Benchmark code based on matchit and actix-router code, so they are also here for reference.

Usage

use squall_router::SquallRouter;

fn main() {
    let mut router = SquallRouter::new();
    router.set_ignore_trailing_slashes();

    router
        .add_validator("int".to_string(), r"[0-9]+".to_string())
        .unwrap();
    router
        .add_validator(
            "uuid".to_string(),
            r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}".to_string(),
        )
        .unwrap();

    router.add_route(
        "GET".to_string(),
        "/route/without/dynamic/octets".to_string(),
        0,
    );
    router.add_route(
        "GET".to_string(),
        "/route/aaa/{string_param}/bbb/{num_param:int}/ccc/{uuid_param:uuid}".to_string(),
        1,
    );
    router.add_location("GET".to_string(), "/files/css".to_string(), 2);

    let (handler_0, _parameters_0) = router
        .resolve("GET", "/route/without/dynamic/octets")
        .unwrap();
    assert_eq!(handler_0, 0);

    let (handler_1, parameters_1) = router
        .resolve(
            "GET",
            "/route/aaa/aaa_value/bbb/1234/ccc/4bea5a51-1b80-4433-be06-d52726015591",
        )
        .unwrap();
    assert_eq!(handler_1, 1);
    assert_eq!(parameters_1, vec![
        ("string_param", "aaa_value"),
        ("num_param", "1234"),
        ("uuid_param", "4bea5a51-1b80-4433-be06-d52726015591")]
    );

    let (handler_2, _parameters_2) = router
        .resolve("GET", "/files/css/vendor/style.css")
        .unwrap();
    assert_eq!(handler_2, 2);
}

HashDoS safety note

Crate's routing database based on rustc_hash::FxHashMap which is non-cryptographic hash.

It is applicable and safe here because the database is filled only by endpoints registration and not during requests handling.

Limitations

At the moment, there are two well known limitations

Equal length routes with mixed dynamic and static parameters in the same position

/api/v1/user/{user_id}/info/full

/api/v1/user/parameter/{prm}/details

In case of such API design request to /api/v1/user/parameter/info/full will not find the handler. It is applicable only for routes with at least one dynamic parameter and with the same amount of octets. We have checked how many APIs have such behavior, it is less than 1% which can be easily aligned with this contract.

In the next releases, it will be covered by assertion to prevent a bad user experience.

There are a few ways under discussion how to make such limitations not applicable.

Wildcard route suffix

/static/{path:.*} - Dynamic routing part depends on octets splitting so it is not applicable.

Instead, you should use location api.

Commit count: 26

cargo fmt