2 unstable releases
0.2.2 | Jan 15, 2022 |
---|---|
0.2.1 |
|
0.1.0 | Dec 22, 2021 |
#18 in #http-router
71KB
657 lines
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.
Dependencies
~2.2–4MB
~64K SLoC