net
Standing up a TCP server or an HTTP endpoint should not need external dependencies. net provides addressing and sockets at the base, with TLS and the HTTP, HTTP/2, and WebSocket layers on top.
public module net::addr;
public module net::socket;
public module net::dns;
public module net::tls;
public module net::https;
public module net::ws;
public module net::http;
public module net::http2;
| Module | What it holds |
|---|---|
net::addr | IpAddr, SocketAddr, Url |
net::socket | TcpStream, TcpListener, UdpSocket, and their futures |
dns | resolve, lookup, and the async Resolve |
net::tls | TlsConnector, TlsAcceptor, TlsStream over OpenSSL |
https | HttpsClient, one-shot requests over TLS |
net::http | HTTP/1.1: Request, Response, Router, HttpServer, Client |
net::http2 | HTTP/2 with HPACK: Http2Connection, Http2Client, Http2Server |
net::ws | WebSocket: WebSocket<S>, Message |
net::sys is the per-platform socket shim underneath all of it — BSD sockets on POSIX, Winsock on Windows — and has no page; nothing in it is meant to be called from outside net.
Everything that waits is a future
There is deliberately no blocking connect, accept, read, or write anywhere in net. bind stays a plain call because binding does not wait; every other operation is a future — TcpConnect, TcpAccept, TcpRead, TcpWrite, and their UDP and TLS counterparts — driven by an Executor whose reactor the operation registers with when the socket is not ready.
Each operation owns everything it touches — the socket, and for a read or write the buffer too — and hands it all back in its output rather than borrowing. Cryo has no lifetimes, so a borrowing future could not express "the socket outlives this operation", and a buffer named by a raw pointer into the caller's frame would be written through a stale address once the future is moved between polls. Owning the handle also states the discipline: while an operation is in flight nobody else holds that socket, so two reads cannot interleave on one connection.
In practice you rarely drive these futures by hand. TcpStream and TlsStream implement AsyncTransport, so wrapping one in a BufStream gives you read_line, read_exact, send, and the rest — and that is what the HTTP and WebSocket layers do.
Deliberately out of scope
- Unix-domain and raw sockets. Easy to add; nothing has needed them.
- Chunked transfer-encoding and HTTP/1.1 pipelining. The HTTP/1.1 server does keep-alive — persistent connections, read timeouts,
Connection: closeopt-out — but each request within a connection is still served sequentially. - URL percent-decoding and normalisation.
net::addr::urlreturns parts verbatim.
public function name() { ... }
public:Visible to any module that imports this one - the default for top-level items and for struct fields, so it is mostly written to be explicit. public: opens a visibility block that groups the members after it.
public module collections::array;In a directory's _module.cryo aggregator, lists a submodule and whether it is public. Importing the directory's namespace sees only the modules declared public here.
namespace std::net::http2;net::http2: HTTP/2 (RFC 7540) with HPACK header compression (RFC 7541).
module pagetype enum IpAddr {
V4(IpV4Addr);
V6(IpV6Addr);
}type struct Url {
scheme: Str;
host: Str;
port: u16;
path: Str;
query: Option<Str>;
fragment: Option<Str>;
}net::addr::url: a parser for absolute URLs.
std::net::addr::urltype struct TcpStream {
fd: i32;
}type struct TcpListener {
fd: i32;
}type struct UdpSocket {
fd: i32;
}type struct Resolve {
host: Str;
job: ResolveJob*;
}Resolve host without stalling the executor: the lookup runs on the BlockingPool's threads and the task parks until it answers.
type struct TlsConnector {
ctx: void*;
verify: boolean;
}type struct TlsAcceptor {
ctx: void*;
}type struct TlsStream {
ssl: void*;
inner: TcpStream;
}type struct HttpsClient {
connector: TlsConnector;
}type struct Request {
method: Method;
path: String;
version: String;
headers: Headers;
body: Array<u8>;
params: HashMap<String, String>;
}HTTP request.
std::net::http::requesttype struct Response {
status: StatusCode;
version: String;
headers: Headers;
body: Array<u8>;
}HTTP response.
std::net::http::responsetype struct Router {
routes: Array<Route>;
}Method- and path-based request dispatcher.
std::net::http::routertype struct HttpServer {
addr: SocketAddr;
router: Router*;
read_timeout_secs: i64;
}A configured HTTP server ready to bind and run.
std::net::http::servertype struct Http2Connection<S> {
conn: BufStream<S>;
encoder: HpackEncoder;
decoder: HpackDecoder;
is_client: boolean;
next_stream_id: u32;
goaway_received: boolean;
remote_max_frame: u64;
remote_initial_window: i64;
conn_send_window: i64;
stream_send_window: i64;
}type struct Http2Server {
listener: TcpListener;
}A single-connection-at-a-time h2c (cleartext) server.
std::net::http2::servertype struct WebSocket<S> {
conn: BufStream<S>;
is_client: boolean;
closed: boolean;
}type enum Message {
Text(String);
Binary(Array<u8>);
Close;
}A complete application message. Owns its payload.
std::net::ws::connnamespace std::net::sys;net::sys: per-OS socket primitives behind a uniform interface.
type struct TcpConnect {
fd: i32;
addr: [u8;
28];
addrlen: u32;
err: IoError;
rt: Reactor*;
}Connect to a remote address without blocking. The socket is opened and the handshake started when the future is created; each poll asks the OS whether it finished, parking on writability in between. The socket is closed if the future is dropped before it completes.
std::net::socket::tcptype struct TcpAccept {
listener: TcpListener;
rt: Reactor*;
}Accept the next incoming connection. An accepted socket does not inherit the listener's non-blocking mode, so it is set here — it is about to be used with these same futures. Owns the listener for the duration and hands it back in the TcpAccepted, so a server loop starts the next accept from that.
type struct TcpRead {
stream: TcpStream;
buf: Array<u8>;
rt: Reactor*;
}Read as soon as the socket has anything to give. Completes with the byte count — 0 means the peer closed its end — or the error that ended it.
std::net::socket::tcptype struct TcpWrite {
stream: TcpStream;
buf: Array<u8>;
rt: Reactor*;
}Write as soon as the socket has room. Completes with the number of bytes accepted, which may be fewer than asked for — a caller with more to send starts another write for the remainder, which is why the buffer comes back untouched. Owns the socket and the buffer exactly as TcpRead does, and for the same reason.