dce-router 1.8.0

A router for all type programming api route.
Documentation

中文文档

DCE

DCE is a routing framework for all kinds of routable interfaces. Almost every application provides interactive interfaces, but because different protocols (HTTP, CLI, TCP, WebSocket, ...) have different addressing rules, there is no unified routing rule across them. That leads to chaotic program structures and makes it hard to control every interface in a unified and convenient way.

DCE solves this by providing a standard router and encapsulating the routable protocol: as long as an interface implements the RoutableProtocol trait, it can be uniformly routed through the DCE router. For protocols without a standard URI path (such as CLI or custom binary protocols), you implement the trait yourself and the router will handle them the same way as HTTP.

The router package lives in crates/router. Working protocol implementations are located in crates/protocols, converters in crates/converter, macros in crates/macro, and the session/JWT library in crates/auth.

Features

  • Unified routing for every routable protocol: HTTP (dce-hyper), CLI (dce-cli), and custom protocols.
  • Fast path matching: regular paths match from the API hash map in O(1); variable paths scale with the number of peer variables.
  • Variable paths: required {name}, optional {name?}, vector {name+}, emptable vector {name*}.
  • Suffix routing: route by file-like suffixes such as file.txt, file.txt|json.
  • Omission: mark a path segment as omitable so it can be skipped during matching.
  • API metadata: every API can carry an id, name, methods, redirect, and arbitrary extra data (for docs, permissions, metrics, ...).
  • Global middleware: pre/post handlers can be attached to paths (including wildcard paths) for things like global permission control.
  • Converters & serializers: DTO ↔ entity conversion with desensitization, plus pluggable serializers (JSON, Protobuf) for request decoding and response building.
  • Macros: the #[api] attribute macro turns any function into a routable API; error macros generate public/private error messages.
  • Session & JWT: user session, renewable/rotating JSON Web Token, in-memory (SHM) or Redis storage.

Workspace layout

[ROOT]
├── crates                              Crate directory
│   ├── macro                           dce-macro, attribute and error macros
│   ├── router                          dce-router, the core router
│   ├── converter                       dce-converter, JSON / Protobuf converters
│   ├── auth                            dce-session, session, user & JWT library
│   ├── util                            dce-util, shared utilities
│   └── protocols                       Routable protocol implementations
│       ├── cli                         dce-cli, CLI routable protocol
│       └── hyper                       dce-hyper, HTTP routable protocol
├── examples                            Runnable example binaries
│   ├── async-http.rs                   Async HTTP + CLI example
│   └── sync-http.rs                    HTTP example with sync handlers
└── src                                 Reserved for the DCE command line tools

The src directory is reserved for DCE command line tools that are planned to live in this repository, so it currently only contains an empty library root.

Getting started

Add the crates you need to your Cargo.toml:

[dependencies]
dce-router = { path = "crates/router", version = "^1.8" }
dce-macro = { path = "crates/macro", version = "1.*" }
dce-util = { path = "crates/util", version = "^1.8" }
dce-cli = { path = "crates/protocols/cli", version = "^1.8" }
tokio = { version = "^1.49", features = ["full"] }

Define APIs with the #[api] macro, register them on the router, then route incoming requests:

use dce_cli::{cli_route, CliProtocol, CliRouter};
use dce_macro::api;
use dce_router::context::Request;
use dce_util::result::DceVoid;

#[tokio::main]
async fn main() {
    CliRouter.write().unwrap()
        .register(hello_api)
        .ready();

    cli_route().await;
}

/// cargo run -- hello
/// cargo run -- hello --target DCE
#[api("hello")]
async fn hello(mut req: Request<'_, CliProtocol>) -> DceVoid {
    let target = req.arg_or("--target", "DCE").to_owned();
    req.write_string(format!("Hello {} !", target));
    Ok(())
}

The #[api] macro generates a fn <name>_api() -> Api<Rp> supplier for every annotated function, which is then fed to the router through Router::register. Alternatively you can build an Api manually and use Router::bind, Router::bind_api, or the convenient HttpRouter::get/post/... methods.

HTTP example

use dce_converter::json::{JsonDeserializer, JsonSerializer};
use dce_hyper::{hyper_route, HyperProtocol, HyperRouter, Method};
use dce_macro::api;
use dce_router::context::Request;
use dce_util::result::DceVoid;

HyperRouter.write().await
    .register(hello_api)
    .register(hello_post_api)
    .ready();

// serve any `hyper::Request` via `hyper_route`
// hyper1::Builder::new().serve_connection(io, service_fn(|req| hyper_route(req, Default::default())))
/// curl http://127.0.0.1:2046/hello
#[api("hello", methods = Method::Get | Method::Head)]
async fn hello(mut req: Request<'_, HyperProtocol>) -> DceVoid {
    let mut js = JsonSerializer::builder::<_, GreetingResp>(&mut req);
    js.response(GreetingResp { user: "Dce".to_string(), welcome: "Welcome to DCE !".to_string() })
}

/// curl -X POST http://127.0.0.1:2046/hello -H "Content-Type: application/json" -d '{"user":"Drunk","age":18}'
#[api("hello", methods = Method::Post | Method::Options)]
async fn hello_post(mut req: Request<'_, HyperProtocol>) -> DceVoid {
    let body: GreetingReq = JsonDeserializer::decoder(&mut req).deserialize().await?;
    let mut js = JsonSerializer::builder::<_, GreetingResp>(&mut req);
    if body.age < 18 {
        js.fail("only adults allowed".to_string(), 400)
    } else {
        js.response(GreetingResp { user: body.user.clone(), welcome: format!("Hello {}, welcome", body.user) })
    }
}

Run the full runnable examples:

cargo run --example async-http          # CLI router, try: cargo run --example async-http -- hello
cargo run --example async-http -- start/http   # start the HTTP server
cargo run --example sync-http           # HTTP server with sync handlers

Routing rules

The path grammar mirrors a URI but is shared by every protocol:

Syntax Meaning
hello fixed segment
{name} required variable segment
{name?} optional variable segment (only at the end)
{name+} vector of one or more segments
{name*} vector of zero or more segments
file.txt suffix routing
file.txt|json multiple suffixes
omission = true the segment may be omitted during matching

Note: optional (?) and vector (+/*) variables are only allowed at the end of a path, because an ambiguous variable in the middle cannot be resolved unambiguously.

APIs are matched by path first; when several APIs share a path, RoutableProtocol::match_api (for HTTP, method and host matching) selects the final one. A matched API may also redirect to another path, so legacy routes can forward transparently.

Converters & serializers

The RequestDecoder and ResponseBuilder types in dce-router decouple request/response payloads from your business types:

  • Deserializer decodes raw bytes into a request DTO.
  • Serializer encodes a response DTO into raw bytes.
  • Status renders the unified {status, code, msg, data} response envelope.

dce-converter ships JSON and Protobuf implementations. Because the mapping between DTO and entity is done through the standard From/Into traits, you can freely desensitize or reshape entity data before it is transmitted.

Session & JWT

The session library provides basic session storage, user sessions, long-lived connection sessions, and self-regenerating (rotating) sessions, so user-level permission control plugs cleanly into the router. The JWT implementation supports HS256/384/512 signing and rotation.

Performance

Because the process chain is very short — the router calls the controller directly after matching the API by path — performance is very high. Regular paths are matched from the API hash map in O(1); a single-variable path costs O(n); multiple variables scale exponentially with the number of peer variables. Prefer regular paths, or keep the number of variables and peer variables small, to get the best routing performance.

License

MIT