1 n__http__router_8h
gullradriel edited this page 2026-10-25 10:46:33 +01:00

include/nilorea/n_http_router.h

HTTP router: static route tables, typed placeholders, specificity. More...

Structures

Name
struct N_ROUTE_MATCH
a successful match: the route and its decoded placeholder values
struct N_ROUTE
one route of a table

Detailed Description

HTTP router: static route tables, typed placeholders, specificity.

Author: Castagnier Mickael

Version: 1.0

Date: 2026

Routes are declared in static tables, one line each:

static const N_ROUTE PUBLIC[] = {
    {N_ROUTE_GET, "/", h_home, NULL},
    {N_ROUTE_GET, "/{y:d4}/{m:d2}/{d:d2}/{slug:slug}/", h_post, NULL},
    {N_ROUTE_GET, "/media/{id:hex16}/{w:int4}/{name:seg}", h_variant, NULL},
};

A pattern is a '/'-separated list of segments, each either a literal ("feed", matched exactly and case-sensitively against the raw path) or one placeholder "{name:type}" ("{name}" is "{name:seg}"). A trailing '/' in the pattern requires one in the path. Types are checked on the RAW segment, before decoding, so a percent-encoded byte never satisfies a digit, hex or slug type:

type raw segment
dN exactly N digits (N 1..18)
hexN exactly N lowercase hex digits (N 1..64)
intN 1 to N digits (N 1..18)
slug 1 to 120 of a-z 0-9 '_' '-'
seg 1 to 255 bytes, no '/'
rest the remainder of the path, '/' included, last only

Captured values are strictly percent-decoded into the match (a malformed escape, a decoded NUL or ill-formed UTF-8 in ANY segment of the path makes the whole request a 400, before any route is tried).

When several routes match, the most specific wins, segment by segment from the left: a literal beats dN/hexN, which beat intN, then slug, seg, rest. So "/feed/" wins over "/{slug}/" whatever the table order, and n_router_check() refuses only real ambiguities (two routes the rule cannot order, like a duplicate or d4 against hex4). A route allowing GET also answers HEAD.

The router does no allocation per request and keeps pointers to the route tables: they must outlive it (static tables do).

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#ifndef __NILOREA_HTTP_ROUTER_HEADER
#define __NILOREA_HTTP_ROUTER_HEADER

#ifdef __cplusplus
extern "C" {
#endif


#include "nilorea/n_http_req.h"

#include <stddef.h>

#define N_ROUTE_GET N_HTTP_REQ_METHOD_BIT(N_HTTP_REQ_GET)
#define N_ROUTE_POST N_HTTP_REQ_METHOD_BIT(N_HTTP_REQ_POST)
#define N_ROUTE_HEAD N_HTTP_REQ_METHOD_BIT(N_HTTP_REQ_HEAD)

#define N_ROUTE_PARAMS_MAX 8
#define N_ROUTE_NAME_MAX 31

struct N_ROUTE_MATCH;

typedef int (*n_route_handler)(void* ctx, const struct N_ROUTE_MATCH* match);

typedef struct N_ROUTE {
    unsigned methods;
    const char* pattern;
    n_route_handler handler;
    const void* data;
} N_ROUTE;

typedef struct N_ROUTE_MATCH {
    const N_ROUTE* route;
    size_t nb_params;
    const char* names[N_ROUTE_PARAMS_MAX];
    const char* values[N_ROUTE_PARAMS_MAX];
    size_t lens[N_ROUTE_PARAMS_MAX];
    char buf[N_HTTP_REQ_PATH_MAX + N_ROUTE_PARAMS_MAX + 1];
} N_ROUTE_MATCH;

typedef enum {
    N_ROUTER_FOUND = 0,              
    N_ROUTER_NOT_FOUND = 1,          
    N_ROUTER_METHOD_NOT_ALLOWED = 2, 
    N_ROUTER_BAD_REQUEST = 3         
} N_ROUTER_RESULT;

typedef struct N_ROUTER N_ROUTER;

N_ROUTER* n_router_new(void);

void n_router_free(N_ROUTER** r);

int n_router_add(N_ROUTER* r, const N_ROUTE* routes, size_t count);

int n_router_check(const N_ROUTER* r, char* err, size_t errsz);

size_t n_router_count(const N_ROUTER* r);

const N_ROUTE* n_router_route(const N_ROUTER* r, size_t i);

N_ROUTER_RESULT n_router_match(const N_ROUTER* r, n_http_req_method method, const char* path, size_t len, N_ROUTE_MATCH* match, unsigned* allowed);

const char* n_route_param(const N_ROUTE_MATCH* m, const char* name, size_t* len);

int n_router_allow_header(unsigned allowed, char* out, size_t size);


#ifdef __cplusplus
}
#endif

#endif /* __NILOREA_HTTP_ROUTER_HEADER */