Skip to main content

Wrouter: Symbolic Web Router

Project status: This is in pre-release. 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.

Install from PyPI with pip install wrouter.

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, but ticket:ticket_id will be parsed as a literal!)
  • /board/:board_id/ticket/:ticket_id

Routes must not conflict with parameters and literals at the same level.

  • /project/list and - /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/list and /project/*
  • /project/:project_id and /project/* (incompatible!)

Parameters at the same level must share the same name.

  • /repos/:user/:repo and /repos/:user/:repo/tree/:branch/*
  • /project/:id and /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

Metadata

Release files for wrouter 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wrouter 1.0.0
File Size Uploaded Provenance Actions
wrouter-1.0.0.tar.gz 49.8 kB Without provenance Details for wrouter-1.0.0.tar.gz

Release files / wrouter-1.0.0.tar.gz

Download URL wrouter-1.0.0.tar.gz
Size 49.8 kB
Tags Source
SHA-256 checksum
How to use checksums
1b6f45c6b9fd6e13990e9a5020fda71a816e5676d66dbf3826e44d2985625b86
BLAKE2b-256 checksum
How to use checksums
963569485941ce6552134eb815f419923c34b4fe4831c1c656702a3a5435fc97
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.12

Security for wrouter 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

1 release file No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page