std1.0.0-beta-7 SafeC docs

Source http/router.h

View in the repository

// SafeC Standard Library — HTTP routing and middleware.
//
// A Router maps (method, path pattern) to handlers — patterns as in
// std/http/route.h (":id" captures a segment, "*" / "*rest" the rest of
// the path) — runs a middleware chain around them, and serves itself on an
// HttpServer (std/http/http.h):
//
//   struct HttpResponse get_user(&HttpRequest req, &HttpCtx ctx) {
//       return std::HttpResponse::text(200, ctx.param_or("id", "?"));
//   }
//
//   struct Router r = std::Router::new();
//   r.use_logger();
//   r.use_cors("*", "GET, POST", "Content-Type");
//   r.get("/users/:id", get_user);
//   r.static_dir("/assets", "./public");
//   struct HttpServerConfig cfg = std::HttpServerConfig::defaults(8080);
//   r.listen(&cfg);                                  // or r.start(&srv) + srv.stop()
//
// Matching. The most specific matching route wins (literal segments beat
// ":param", which beats "*"), whatever the registration order. A path
// that matches some route but not for this method gets 405 with an Allow
// header (HEAD falls back to a GET route; OPTIONS without a route of its
// own answers 204 with Allow); no match at all gets the not-found handler
// (a plain 404 by default).
//
// Middleware is fn(req, ctx, next): call next.run(req, ctx) to continue
// down the chain (and eventually to the route), or return a response of
// your own to short-circuit. Middleware runs in registration order for
// every request, matched or not, and sees ctx.params of the matched route.
//
// Handlers are plain function pointers (no closures), so per-application
// state lives in globals or in what the handler can reach from the
// request; ctx.state (set_state) carries one pointer-sized value for the
// whole router.
#pragma once
#include <std/http/http.h>
#include <std/http/route.h>
#include <std/http/static_files.h>
#include <std/collections/string.h>
#include <std/collections/vec.h>

namespace std {

// Per-request context passed to route handlers and middleware.
struct HttpCtx {
    &HttpConn conn;                    // the connection (peer, keep-alive, detach)
    struct Vec<struct RouteParam> params;
    int       route_index;             // index of the matched route, -1 if none
    long long start_ns;                // when dispatch began (monotonic clock)
    ?&Erased  router_;                 // internal: the Router

    // A captured path parameter (":name" / "*name"; "*" for a bare
    // wildcard), percent-decoded, or null.
    const ?&char param(const &char name) const;
    const &char  param_or(const &char name, const &char fallback) const;
    // The pattern of the matched route ("" if none).
    const &char  pattern() const;
};

typedef fn struct HttpResponse(&HttpRequest req, &HttpCtx ctx) RouteHandler;

// The rest of the middleware chain.
struct HttpNext {
    ?&Erased router_;   // internal: the Router
    int      index_;    // internal: next middleware to run

    struct HttpResponse run(&HttpRequest req, &HttpCtx ctx);
};

typedef fn struct HttpResponse(&HttpRequest req, &HttpCtx ctx, &HttpNext next) Middleware;

struct HttpRoute_ {
    struct String method;     // "" = any method
    struct String pattern;
    RouteHandler  handler;
    int           kind;       // 0 handler, 1 static directory
    struct String dir;        // kind 1
};

struct Router {
    struct Vec<struct HttpRoute_> routes;
    struct Vec<Middleware>        middleware;
    RouteHandler                  not_found;      // set_not_found; plain 404 when unset
    int                           has_not_found;
    struct StaticOptions          static_opts;    // for static_dir routes
    // Built-in middleware settings (use_cors / use_body_limit / use_gzip).
    struct String                 cors_origin;
    struct String                 cors_methods;
    struct String                 cors_headers;
    unsigned long                 body_limit;
    unsigned long                 gzip_min;
    ?&Erased                      state;          // set_state / router_state (erased, see std/erased.h)

    static struct Router new();

    // Registers 'handler' for 'method' ("GET", ...; "*" = any) and
    // 'pattern'. Later registrations of the same method+pattern replace
    // earlier ones.
    void route(const &char method, const &char pattern, RouteHandler handler);
    void get(const &char pattern, RouteHandler handler);
    void post(const &char pattern, RouteHandler handler);
    void put(const &char pattern, RouteHandler handler);
    void patch(const &char pattern, RouteHandler handler);
    void del(const &char pattern, RouteHandler handler);       // DELETE
    void options(const &char pattern, RouteHandler handler);
    void any(const &char pattern, RouteHandler handler);
    // Serves files under 'dir' for GET/HEAD below URL 'prefix' ("/assets"
    // -> "/assets/*"), through http_serve_dir with static_opts.
    void static_dir(const &char prefix, const &char dir);
    void set_not_found(RouteHandler handler);

    // Appends a middleware to the chain.
    void use(Middleware mw);
    // Built-ins:
    // one log line per request through std/log.h (LOG_LEVEL_INFO, tag
    // "http"): "GET /path -> 200 (1.2 ms, 512 B)".
    void use_logger();
    // CORS: answers preflights (std/http/cors.h) and adds
    // Access-Control-Allow-Origin & co. to every response.
    void use_cors(const &char origin, const &char methods, const &char headers);
    // 413 for request bodies over 'max_bytes' (on top of the server's own
    // max_body_bytes).
    void use_body_limit(unsigned long max_bytes);
    // gzip-compresses compressible responses of at least 'min_bytes' when
    // the client sends Accept-Encoding: gzip (adds Vary: Accept-Encoding).
    void use_gzip(unsigned long min_bytes);
    // Recovers nothing (SafeC has no exceptions) but turns a handler's
    // status 0 response into 500 — a cheap guard for "forgot to set
    // status".
    void use_status_guard();

    // Runs the chain for one request (what the server calls; also handy
    // in tests: build an HttpRequest::new and an HttpConn by hand).
    struct HttpResponse dispatch(&HttpRequest req, &HttpConn conn);

    // Serves this router on a bound server (blocking / background). The
    // Router must outlive the server.
    int serve(&HttpServer srv);
    int start(&HttpServer srv);
    // HttpServer::bind + serve; -1 if the bind fails.
    int listen(const &HttpServerConfig cfg);
    void drop();

    void free() consume;   // = drop()
};

// Stores/reads the router's one application pointer (shared by every
// request; synchronize it yourself if handlers mutate what it points at).
generic<T>
void router_set_state(&Router r, ?&T state) { unsafe { r.state = (?&Erased)state; } }
generic<T>
?&T router_state(&HttpCtx ctx) {
    unsafe {
        struct Router* r = (struct Router*)ctx.router_;
        return (?&T)r->state;
    }
}

// A ready-made HttpConn for calling Router::dispatch outside a server
// (fd -1, nothing detachable).
struct HttpConn http_conn_none();

} // namespace std