Skip to content
CryoCryo home
StdlibNetworking

net::http

Enough HTTP to stand up a handler-based server and make client requests with no external dependencies. HTTP/1.1 with keep-alive lives in net::http; HTTP/2 and WebSocket are the sibling modules net::http2 and net::ws.

Everything here builds on the transports in net, so the same code runs over plaintext TCP or TLS.

ItemImport
Methodimport std::net::http::method;
StatusCodeimport std::net::http::status;
Headersimport std::net::http::headers;
Requestimport std::net::http::request;
Responseimport std::net::http::response;
Router, RouteKindimport std::net::http::router;
HttpServerimport std::net::http::server;
Client, sendimport std::net::http::client;
Http2Client, Http2Serverimport std::net::http2;
WebSocket, Messageimport std::net::ws;

A server

import std::net::http;
import std::future;

function hello(req: Request) -> Response {
    return Response::text(StatusCode::ok(), Str::new("hi"));
}

function main() -> i32 {
    mut router: Router = Router::new();
    router.get(Str::new("/"), hello);
    router.get(Str::new("/users/:id"), show_user);

    mut server: HttpServer = HttpServer::with_router(addr, &router);
    mut ex: Executor = Executor::new();
    ex.block_on(server.run());
    return 0;
}

run() is an async method and needs an Executor — its reactor is what the accept and the per-connection reads register with. Connections are still served one at a time: this layer is on the async transport, not yet concurrent across connections.

Two entry points. HttpServer::new(addr, handler) takes a raw function-pointer handler, with all routing and method checking inside it. HttpServer::with_router(addr, &router) delegates to a Router.

The router must outlive the server — declare both in the same scope. The server holds a pointer, not ownership.

with_ready(cb) attaches a callback invoked once after the socket binds and before the first accept: log the listening address, write a pid-file, signal a health check. with_read_timeout(secs) sets the per-request read deadline; zero disables it. Both return the server for chaining. run_on(listener) runs against a listener you already have.

Router

Register routes with the per-method helpers, then hand a pointer to the server. Routes are evaluated in registration order; the first route where both method and path match wins.

/                     exact root
/users                exact match
/users/:id            `:id` captures any non-empty segment
/files/:dir/:name     two named captures

get, post, put, del, patch, head, and options each take a pattern and a (Request) -> Response handler; route(method, pattern, handler) is the generic form. Each also has an overload taking a RouteKind, which is how you register a prefix route — one that matches as long as the pattern is a prefix of the request path, accepting trailing segments.

on_not_found(handler) and on_method_not_allowed(handler) override the fallbacks. dispatch(req) is what the server calls.

Captured segments come back through Request::param:

function show_user(req: Request) -> Response {
    match (req.param(Str::new("id"))) {
        Option::Some(id) => { return Response::text(StatusCode::ok(), id.as_str()); }
        Option::None     => { return Response::new(StatusCode::bad_request()); }
    }
}

Method

An enum over the standard verbs, with as_str(), static parse(source: Str) -> Result<Method, ConversionError>, and parse_opt.

StatusCode

A wrapper over the numeric code with named constructors across the ranges — ok, created, accepted, no_content, moved_permanently, found, not_modified, temporary_redirect, permanent_redirect, bad_request, unauthorized, forbidden, not_found, method_not_allowed, conflict, length_required, payload_too_large, unsupported_media_type, too_many_requests, internal_server_error, not_implemented, bad_gateway, service_unavailable, gateway_timeout, plus continue_ and switching_protocols.

The class predicates are is_informational, is_success, is_redirection, is_client_error, and is_server_error. reason_phrase() gives the standard text.

Headers

A case-insensitive map. new(), length(), is_empty(), insert(name, value), get(name) -> Option<String>, contains(name), remove(name) -> boolean, and encode_into(out) to serialize every header as name: value\r\n.

content_length() returns Result<Option<u64>, IoError> — absent is Ok(None), malformed is an error.

Request and Response

Request::new(method, path) builds one; with_header(name, value) and with_body(body) chain, and set_body(body) mutates in place. param(name) reads a router capture. encode_into(out) writes the complete HTTP/1.1 message.

Response::new(status) is the bare form; Response::text(status, body: Str) and Response::json(status, body: Array<u8>) set the body and the matching content type. set_body and encode_into mirror the request side.

Both parse incoming messages through from_request_line / from_status_line plus the header and body reads the server and client drive.

MAX_HEADER_LINE and MAX_BODY bound what will be accepted.

Client

import std::net::http::client;

mut c: Client = Client::new();
const resp = ex.block_on(c.get(addr, Str::new("/health")));

Client::new(), then the async methods get(addr, path) and post(addr, path, body, content_type). The free async function send(addr: SocketAddr, req: Request) -> Result<Response, IoError> sends a request you built yourself.

The Host header is filled in from the address automatically.

For HTTPS, net::https wraps the same machinery with TLS underneath.

HTTP/2

net::http2 is a binary framing layer multiplexing many request/response streams over one connection, with HPACK header compression (RFC 7541) over the RFC 7540 framing.

Http2Connection<S> runs over any Read + Write transport, so the same code speaks h2c — prior-knowledge cleartext over TcpStream — and h2 over a TlsStream once ALPN has negotiated h2.

Http2Client::new() with get(addr, authority, path) and post(addr, authority, path, body, content_type) drives requests. Http2Server::bind(addr) with serve_one(handler) or run(handler) accepts a connection and dispatches each stream to a (Request) -> Response handler.

WebSocket

net::ws speaks RFC 6455 over any Read + Write transport, so the same code handles ws:// over TCP and wss:// over TLS. It is non-blocking throughout, over one buffered connection: a WebSocket<S> owns a BufStream<S>.

import std::net::ws;

mut ws: WebSocket<TcpStream> = WebSocket<TcpStream>::of(conn, true);
await ws.client_handshake(host, path);
await ws.send_text(Str::new("hello"));

match (await ws.recv()) {
    Result::Ok(msg) => { /* Message::Text / Binary / Ping / Pong / Close */ }
    Result::Err(e)  => { }
}

static of(conn, is_client), then client_handshake(host, path) or server_handshake(). The handshake is an HTTP/1.1 Upgrade.

The message surface is send_text(text: Str), send_binary(data: Slice<u8>), ping(data), close(code: u16, reason: Str), and recv() -> Result<Message, IoError> — all async. Framing covers text and binary messages, fragmentation reassembly, and the Ping/Pong/Close control frames. is_closed() and connection() round it out.

SHA-1 and base64 for the accept key are pure Cryo from encoding, so plain ws:// pulls in no crypto dependency at all.