n_http_router: a blog's route table, typed placeholders checked on the raw path, specificity instead of table order, 404 / 405 / 400, HEAD on GET routes, ambiguity and pattern checks
n_http_router: a blog's route table, typed placeholders checked on the raw path, specificity instead of table order, 404 / 405 / 400, HEAD on GET routes, ambiguity and pattern checks
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
if (!ok) {
} else {
}
}
int opt = 0;
while ((opt = getopt(argc, argv, "V:")) != -1) {
if (opt == 'V') {
} else {
return -1;
}
}
return 0;
}
(void)ctx;
(void)m;
return 0;
}
{
N_ROUTE_GET,
"/category/{slug:slug}/page/{n:int6}/",
h,
"category-paged"},
{
N_ROUTE_GET,
"/{y:d4}/{m:d2}/{d:d2}/{slug:slug}/",
h,
"post"},
{
N_ROUTE_GET,
"/media/{id:hex16}/{w:int4}/{name:seg}",
h,
"variant"},
{
N_ROUTE_GET,
"/wp-content/uploads/{path:rest}",
h,
"legacy-upload"},
};
};
if (!ok) {
n_log(
LOG_ERR,
"FAIL: %s -> result %d, route %s, wanted %s", path, (
int)res,
}
}
size_t len = 0;
return v && len == strlen(want) && strcmp(v, want) == 0;
}
unsigned allowed = 0;
char allow[32];
{
char deep[200];
size_t i, o = 0;
for (i = 0; i < 40; i++) {
deep[o++] = '/';
deep[o++] = 'a';
}
}
}
char err[256];
expect_true(
"same shape twice is ambiguous",
n_router_check(r, err,
sizeof(err)) == FALSE && strstr(err,
"/a/{x:slug}/") != NULL);
{
static const char* bad[] = {
"noslash", "/a//b", "/{x", "/{x}y", "/{}", "/{X:seg}", "/{x:what}", "/{x:d0}", "/{x:d19}", "/{x:hex65}",
"/{x:int}", "/{x:seg}/{x:seg}", "/{r:rest}/more", "/{r:rest}/", "/a%20b", "/a b", "/a?b",
"/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}", "/{abcdefghijklmnopqrstuvwxyz012345}"};
size_t i;
for (i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) {
}
}
}
{
N_ROUTE badbit = {1u << 9,
"/x",
h, NULL};
}
}
int main(
int argc,
char** argv) {
char err[256];
fprintf(stderr, "usage: %s [-V LOG_LEVEL]\n", argv[0]);
return 1;
}
if (err[0])
return 1;
}
return 0;
}
void process_args(int argc, char **argv)
static void expect_true(const char *label, int cond)
static void test_checks(void)
the start-up checks
static int param_is(const N_ROUTE_MATCH *m, const char *name, const char *want)
param name of the last match equals want
static void test_match(const N_ROUTER *r)
matching on the blog's tables
static int h(void *ctx, const N_ROUTE_MATCH *m)
a handler that does nothing (the test only routes)
static const N_ROUTE PUBLIC[]
the public routes of the Nilorea blog, in a deliberately unhelpful order: the catch-all "/{slug}...
static const N_ROUTE ADMIN[]
a few admin routes
static void expect_route(const N_ROUTER *r, n_http_req_method method, const char *path, const char *want, N_ROUTE_MATCH *m)
route path with method and expect a route data tag (NULL: not found)
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
#define LOG_NOTICE
normal but significant condition
#define LOG_INFO
informational
n_http_req_method
The methods accepted.
size_t nb_params
number of placeholders
const N_ROUTE * route
the route that matched
const void * data
caller data for this route (a policy: auth, CSRF, limits...)
#define N_ROUTE_POST
route accepts POST
N_ROUTER_RESULT
result of n_router_match()
N_ROUTER * n_router_new(void)
new, empty router
int n_router_allow_header(unsigned allowed, char *out, size_t size)
write an Allow header value for a method mask (GET implies HEAD)
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)
find the route for a method and a path
#define N_ROUTE_HEAD
route accepts HEAD only (rare: GET routes answer HEAD already)
#define N_ROUTE_GET
route accepts GET (and so HEAD)
int n_router_check(const N_ROUTER *r, char *err, size_t errsz)
refuse two routes sharing a method that can match the same path with the same specificity
void n_router_free(N_ROUTER **r)
free a router and set *r to NULL (the route tables are not touched)
size_t n_router_count(const N_ROUTER *r)
number of routes added
const N_ROUTE * n_router_route(const N_ROUTER *r, size_t i)
the i-th route added
int n_router_add(N_ROUTER *r, const N_ROUTE *routes, size_t count)
compile and add a route table; nothing is added when a route is refused
const char * n_route_param(const N_ROUTE_MATCH *m, const char *name, size_t *len)
decoded value of a placeholder
@ N_ROUTER_BAD_REQUEST
the path itself is invalid: 400
@ N_ROUTER_FOUND
a route matched path and method
@ N_ROUTER_NOT_FOUND
no route matches the path: 404 or a fallback
@ N_ROUTER_METHOD_NOT_ALLOWED
the path matches, the method does not: 405
a successful match: the route and its decoded placeholder values
Common headers and low-level functions & define.
a compiled router (opaque)
HTTP router: static route tables, typed placeholders, specificity.