Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Transport Layer

AFast supports multiple transport layers that can run simultaneously on different ports.

HTTP

HTTP server endpoints:

MethodPathDescription
POST/_apiBinary handler dispatch
GET/_wsWebSocket upgrade (merged mode)
GET/code/{service}/{lang}On-demand code gen (requires code)
GET/docAPI docs index (requires doc)
GET/doc/{service}Service-specific docs (requires doc)
*Ordinary routesRESTful endpoints (requires ordinary-http)
GET/path/:paramWebSocket upgrade for ordinary-ws routes (requires ordinary-ws)
GET/pathSSE stream for ordinary-sse routes (requires ordinary-sse)

HTTP Response Format:

  • Success: [0u8][0i64][data: bytes]
  • Error: [1u8][code: i64][message: bytes]

WebSocket

WS frame format:

Request:  [req_id: SeqId][handler_id: u32][len: Len][payload]
Push:     [0: SeqId][conn_id: u32][len: Len][payload]
Heartbeat:[0xFFFFFFFF: SeqId][len: Len][conn_id1: u32]...

SeqId type is controlled by the seq64 feature (i32 or i64), Len type by the len64 feature (u32 or u64).

TCP

TCP uses 4-byte big-endian length-prefix framing with complete binary payloads per frame. Suitable for embedded devices or raw TCP scenarios.

HTTP + WS Port Merging

When ws_addr and http_addr are set to the same address, AFast merges WebSocket into the HTTP server via HTTP Upgrade:

#![allow(unused)]
fn main() {
// Same port for both HTTP and WebSocket
let app = AFast::new()
    .service(svc)
    .ws("0.0.0.0:5000")
    .http("0.0.0.0:5000");  // Same address, auto-merged
}

TLS / HTTPS

AFast supports TLS/HTTPS via rustls with ALPN negotiation for HTTP/2.

Basic Usage

#![allow(unused)]
fn main() {
let app = AFast::new()
    .service(svc)
    .https("0.0.0.0:5443", "./cert.pem", "./key.pem", None);
}

Graceful Fallback

If certificate files don’t exist, the server automatically falls back to plain HTTP:

afast: TLS cert files not found, starting without encryption: [::]:5443

Hot-Reload Certificates

Reload certificates at runtime via a broadcast channel without restarting:

#![allow(unused)]
fn main() {
let (reload_tx, reload_rx) = tokio::sync::broadcast::channel(1);

let app = AFast::new()
    .https("0.0.0.0:5443", "./cert.pem", "./key.pem", Some(reload_rx));

// Reload with original paths
reload_tx.send(None).unwrap();

// Reload with new paths
reload_tx.send(Some(TlsReloadMessage {
    cert_path: "/new/cert.pem".into(),
    key_path: "/new/key.pem".into(),
})).unwrap();
}

Ordinary HTTP (REST)

With ordinary-http, define RESTful routes using get/post/put/patch/delete inside the service! macro:

#![allow(unused)]
fn main() {
use afast::{get, Query, Param, Body, Header, Json, HttpResult};

#[derive(Deserialize)]
struct UserQuery { page: i64, size: i64 }

#[derive(Serialize)]
struct UserResponse { id: i64, name: String }

#[get(":id")]
async fn get_user(
    state: State<AppState>,
    param: Param<UserPath>,
    query: Query<UserQuery>,
) -> HttpResult<Json<UserResponse>> {
    Ok(Json(UserResponse { id: param.id, name: format!("User {}", param.id) }))
}
}

Response Types

TypeHTTP StatusContent-Type
Json<T>200application/json
Text200text/plain
Html200text/html
File200Custom + Content-Disposition: attachment
Status(code)Custom
Redirect::temporary(url) / Redirect::permanent(url)302 / 301Location header

CORS

Enable CORS (Cross-Origin Resource Sharing) via AFast::cors() (requires the http feature). All HTTP endpoints — including binary /_api, ordinary HTTP routes, and code/doc endpoints — automatically include CORS headers:

#![allow(unused)]
fn main() {
use afast::{AFast, CorsConfig};

// Development: allow all origins
AFast::new()
    .cors(CorsConfig::permissive())
    .http("0.0.0.0:5000")
    .run().await;

// Production: specific origins with credentials
AFast::new()
    .cors(
        CorsConfig::new(vec!["https://example.com", "https://app.example.com"])
            .allow_credentials(true)
            .max_age(7200)
    )
    .http("0.0.0.0:5000")
    .run().await;
}

The server automatically:

  • Responds to OPTIONS preflight requests with 204 No Content
  • Injects Access-Control-Allow-Origin into every HTTP response
  • Sets Access-Control-Allow-Methods, Access-Control-Allow-Headers, and Access-Control-Max-Age for preflight

Security Headers

Every HTTP response includes these security headers by default:

HeaderValue
x-content-type-optionsnosniff
x-frame-optionsDENY
content-security-policydefault-src 'self'

Override via AFast::security_headers():

#![allow(unused)]
fn main() {
AFast::new()
    .security_headers(vec![
        ("x-content-type-options", "nosniff"),
        ("x-frame-options", "SAMEORIGIN"),
        ("content-security-policy", "default-src 'self'; script-src 'self' 'unsafe-inline'"),
    ])
    .http("0.0.0.0:5000")
    .run().await;
}

Ordinary WebSocket

With ordinary-ws, define WebSocket routes using ws inside the service! macro. These routes use text/JSON frames instead of the binary protocol:

#![allow(unused)]
fn main() {
use afast::{ws, WsSender, WsReceiver, WsParam};

#[derive(Deserialize)]
struct ChatParam { room: String }

#[ws(desc("Chat room"))]
async fn chat_ws(
    param: WsParam<ChatParam>,
    sender: WsSender,
    mut receiver: WsReceiver,
) -> afast::Result<()> {
    while let Some(msg) = receiver.recv().await {
        if let afast::WsMessage::Text(text) = msg {
            sender.send_text(format!("[{}] {}", param.0.room, text)).await?;
        }
    }
    Ok(())
}

let svc = service!("chat" => {
    ws("/chat/:room", chat_ws),
});
}

Connect: ws://host:port/chat/general?token=abc

TS/JS clients auto-generate platform-aware WebSocket connection methods, compatible with browsers, UniApp, and WeChat Mini Programs.

Server-Sent Events (SSE)

Requires the ordinary-sse feature. Register SSE routes with sse():

#![allow(unused)]
fn main() {
use afast::{SseSender, Query};

#[derive(Deserialize)]
struct SseQuery { room: Option<String> }

#[afast::sse(desc("SSE event stream"))]
async fn notifications(
    query: Query<SseQuery>,
    sender: SseSender,
) -> afast::Result<()> {
    let room = query.0.room.unwrap_or_default();

    // Send named event
    sender.send_event("connected", &serde_json::json!({"room": room})).await?;

    // Send data event (auto-serialized as JSON)
    let mut count = 0u64;
    loop {
        tokio::time::sleep(Duration::from_secs(1)).await;
        count += 1;
        if sender.send_event("tick", &serde_json::json!({"count": count})).await.is_err() {
            break;  // Client disconnected
        }
    }
    Ok(())
}

let svc = service!("events" => {
    sse("/notifications", notifications),
});
}

Connect: GET http://host:port/notifications?room=general

Response headers:

  • Content-Type: text/event-stream; charset=utf-8
  • Cache-Control: no-cache
  • Connection: keep-alive
  • Transfer-Encoding: chunked

Wire format:

event: connected
data: {"room":"general"}

event: tick
data: {"count":1}

SseSender Methods

MethodDescription
send<T: Serialize>(data)Send a data: event with JSON-serialized value
send_event<T: Serialize>(event, data)Send a named event with JSON-serialized value

SseEvent Fields

FieldTypeWire Format
eventOption<&str>event: name\n
dataStringdata: ...\n
idOption<&str>id: ...\n
retryOption<u64>retry: ...\n

Client Codegen

  • TS/JS: Generates EventSource-based methods
  • KT (OkHttp): Uses okhttp3.sse.EventSource
  • KT (non-OkHttp): Uses java.net.http.HttpClient with BodyHandlers.ofLines()
// TS/JS generated client
const es = client.apis.sse_stream({ room: "general" });
es.addEventListener("connected", (e) => console.log("Connected:", e.data));
es.addEventListener("tick", (e) => console.log("Tick:", JSON.parse(e.data)));
es.close();

Long Connections

Handlers using Receiver/Sender are auto-detected as long-connection mode:

#![allow(unused)]
fn main() {
#[handler(desc("Chat"))]
async fn chat(
    state: State<AppState>,
    auth: Custom<Auth>,
    mut receiver: Receiver,
    sender: Sender,
) -> Result<()> {
    sender.send(b"Welcome!".to_vec()).await?;
    while let Some(msg) = receiver.recv().await {
        sender.send(msg).await?;  // echo
    }
    Ok(())
}
}

Generated clients return a Socket object for long-connection handlers, with send()/close() and onMessage callback.

Copied to clipboard!