Fast and deterministric symbolic Web URL route resolver.
Project description
Wrouter: Symbolic Web Router
Project status: This is in pre-release. PyPI package coming soon. There are unit tests for both the C API and the CPython module.
Usage
Python
from wrouter import build_router, Dispatcher
routes = [
("/account", "account.list"),
("/account/create", "account.create"),
("/account/a/:account_id", "account.view")
]
dispatcher = Dispatcher(build_router(routes))
endpoint, params = dispatcher.resolve("/account/a/1234")
print(f"{endpoint} {params['account_id']}")
Will print: account.view 1234.
C++
#include "wrouter.hpp"
#include <iostream>
#include <string>
struct Response {
std::string body;
};
int main()
{
wrouter::Builder<Response> builder;
builder.add("/hello/:name", [](Response& response, wrouter::Params params) {
response.body = "Hello, " + params["name"] + "!";
});
auto router = builder.consume();
wrouter::Dispatcher dispatcher(router);
Response response;
dispatcher.dispatch("/hello/world", response);
std::cout << response.body << "\n";
}
C
#include <stdio.h>
#include <wrouter.h>
static void route_hello(void *dispatch_ctx, const void *route_ctx, const wrouter_params_t *params)
{
const wrouter_param_t *addressee = wrouter_param(params, "addressee");
if (addressee)
printf("Hello, %.*s!\n", addressee->length, addressee->value);
}
int main(void)
{
wrouter_error_t err;
wrouter_options_t options = { 0 };
wrouter_builder_t *builder = NULL;
wrouter_t *router = NULL;
wrouter_dispatcher_t *dispatcher = NULL;
builder = wrouter_builder_create(options);
if (builder == NULL)
goto failure;
err = wrouter_add_handler(builder, "/hello/:addressee", route_hello);
if (err)
goto failure;
router = wrouter_consume(&builder, &err);
if (err)
goto failure;
dispatcher = wrouter_dispatcher_create(router);
if (dispatcher == NULL)
goto failure;
wrouter_dispatch(dispatcher, "/hello/world", NULL);
wrouter_dispatcher_destroy(&dispatcher);
wrouter_destroy(&router);
return 0;
failure:
fprintf(stderr, "%s\n", wrouter_strerror(err));
wrouter_builder_destroy(&builder);
wrouter_dispatcher_destroy(&dispatcher);
wrouter_destroy(&router);
return 1;
}
Compile with:
gcc -o hello hello.c -lwrouter
Will print: Hello, world!
Concepts
This project is designed as a router for use in a Web application server that is assumed to be behind an HTTP proxy. As such, it makes no attempt to handle hostnames, subdomains, or aliases. The router does not handle HTTP parsing or request handling, and as such should be used in conjunction with other libraries.
The router is intended to be fast, cache-efficient, and deterministic. It is therefore deliberately restrictive in what forms of routes can be accepted into the routing graph (see constraints below). This results in a graph that is simple and easy to traverse efficiently. The strict routing graph prevents ambiguity in route resolution, avoiding the need for prioritisation or resolving the specificity of conflicting routes.
The router has no concept of HTTP methods such as GET and POST. Therefore, a router must be instantiated for each method supported by the application, including a separate router for WebSockets, if desired.
The router is immutable, and is therefore thread-safe, and may be shared between threads. The dispatcher is mutable and must not be shared between threads.
Constraints
A URL is divided into segments by the / character. A segment maybe either be
a literal string, parameter, or wildcard. Parameters are denoted by a colon
(default), angle, or brace; e.g., :param, <param>, or {param},
respectively.
Wildcards (*) may only appear at the end of a route. Wildcards are evaluated
as a fallback. The most specific wildcard will match. A wildcard will consume
all segments following it.
/accounts/user/*/view(invalid!)/accounts/downloads/*/*
A segment may only be a parameter or a literal.
/board/:board_id/ticket:ticket_id(valid, butticket:ticket_idwill be parsed as a literal!)/board/:board_id/ticket/:ticket_id
Routes must not conflict with parameters and literals at the same level.
/project/listand -/project/:project_id(incompatible!)
Wildcards are acceptable within at the same level at the end if they are opposing a literal, not a parameter.
/project/listand/project/*/project/:project_idand/project/*(incompatible!)
Parameters at the same level must share the same name.
/repos/:user/:repoand/repos/:user/:repo/tree/:branch/*/project/:idand/project/:project_id/accounts/:account_id(incompatible!)
Routes with trailing slashes are distinct. The following are not equivalent:
/test/test/
If it is desired to redirect from /test to /test/, this must be performed
explicitly by adding both routes, of which the former will redirect to the
latter.
Flexibility is not a goal of this project.
Where these constraints are severely limiting, the HTTP proxy should rewrite
URLs for the application server to consume. For example, /new could be
rewritten as /repo/new, while /:user/:repo could be rewritten as
/repos/:user/:repo. A small rewrite engine might be included in the future.
Limitations
This is not an HTTP parser; as such, request paths containing query parameters
such as /foo/bar?query=hi will fail to match.
Building
Compile:
make
Run tests:
make test
Install:
sudo make install
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file wrouter-0.1.0.tar.gz.
File metadata
- Download URL: wrouter-0.1.0.tar.gz
- Upload date:
- Size: 47.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a29864a45d9a3f193822862e9714b153ec2c3c21a15d62e7c7fc4b3ee8e250ae
|
|
| MD5 |
98dcd9c46476b34ce56be4059f0a0819
|
|
| BLAKE2b-256 |
a2af8f1632cbf9fb5b9fefd281d765497dc6f3f311d137d6958769a01dad63ff
|