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.
This page covers the transport half: addresses, name resolution, TCP, UDP, and TLS.
| Item | Import |
|---|---|
IpV4Addr, IpV6Addr, IpAddr | import std::net::addr::ip; |
SocketAddr | import std::net::addr::socket_addr; |
Url | import std::net::addr::url; |
TcpStream, TcpListener | import std::net::socket::tcp; |
UdpSocket | import std::net::socket::udp; |
resolve, lookup, resolve_one | import std::net::dns; |
TlsConnector, TlsAcceptor, TlsStream | import std::net::tls; |
HttpsClient | import std::net::https; |
Addressing
IpV4Addr::new(a, b, c, d) builds an address from its octets; localhost(), unspecified(), and broadcast() are the named ones. octet(index), is_loopback(), and is_unspecified() inspect it.
IpV6Addr has localhost(), unspecified(), from_buf(p), group_at(group), and the same predicates.
Both parse from text with static parse(source: Str) -> Result<_, ConversionError> or the Option-returning parse_opt. IpAddr is the enum over the two, with the same parse pair.
SocketAddr::new(ip: IpAddr, port: u16) pairs an address with a port. That is what the socket constructors take.
IPv6 addressing is parsed and represented, but not yet dialable.
Url
net::addr::url parses absolute URLs — scheme://host:port/path?query#frag — into borrowed parts:
type struct Url {
scheme: Str;
host: Str;
port: u16;
path: Str; // never empty; "/" when the URL had none
query: Option<Str>; // without the leading '?'
fragment: Option<Str>; // without the leading '#'
}
Url::parse(source: Str) -> Result<Url, UrlError>.
It is a standalone parser. Parts come back verbatim — no %XX decoding, no ./.. collapsing — and http::Client still takes a SocketAddr plus a path, so wiring a Url-accepting client entry point is a remaining integration step.
TCP
import std::net::socket::tcp;
import std::net::addr::socket_addr;
mut listener: TcpListener = TcpListener::bind(addr)?;
loop {
mut conn: TcpStream = listener.accept()?;
handle(conn);
}
TcpStream implements Read and Write, so everything in io works against it.
TcpStream | Notes |
|---|---|
static connect(remote: SocketAddr) | Result<TcpStream, IoError> |
static from_fd(sock: i32) | Adopt an existing descriptor. |
shutdown_write() | Half-close; the peer sees EOF. |
set_read_timeout(secs, usecs) / set_write_timeout | |
set_nonblocking(on: boolean) |
TcpListener | Notes |
|---|---|
static bind(local: SocketAddr) | Result<TcpListener, IoError> |
accept() | Result<TcpStream, IoError> |
local_addr() | Useful when you bound to port 0. |
set_nonblocking(on: boolean) | |
static from_fd(sock: i32) |
Both close their descriptor on drop.
Async sockets
The same transports have future-based counterparts driven by the reactor: TcpConnect, TcpAccept, TcpRead, and TcpWrite, each with a start(...) constructor and a poll. They take ownership of the socket and the buffer for the duration of the operation and hand both back on completion, which is what lets them be held across a suspension point. Dropping one releases its reactor registration — see cancellation is a drop.
UDP
UdpSocket::bind(local) or unbound() creates a datagram socket.
| Method | Notes |
|---|---|
send_to(bytes: Slice<u8>, dest: SocketAddr) | Result<u64, IoError> |
recv_from(buffer: u8*, length: u64) | Result<(u64, SocketAddr), IoError> |
connect(remote: SocketAddr) | Fix a default peer, enabling send / recv. |
send(bytes) / recv(buffer, length) | For a connected socket. |
local_addr() | Result<SocketAddr, IoError> |
set_read_timeout(secs, usecs) / set_broadcast(on) |
DNS
| Function | Returns |
|---|---|
resolve(host: Str) | Result<ResolvedAddrs, IoError> |
resolve_one(host: Str) | Result<IpAddr, IoError> |
lookup(host: Str, port: u16) | Result<Array<SocketAddr>, IoError> |
ResolvedAddrs carries length() and get(index) -> Option<IpAddr>, and must be dropped. Resolution goes through getaddrinfo, with its error codes mapped into IoError.
TLS
A thin, safe binding over the system OpenSSL — not a reimplementation. TlsConnector (client) and TlsAcceptor (server) mint TlsStreams, which implement Read and Write, so the HTTP and WebSocket layers run over them unchanged. That is the whole point of routing TLS through the same stream traits as plaintext TCP.
import std::net::tls;
mut connector: TlsConnector = TlsConnector::new()?;
mut handshake: TlsHandshake = connector.start_connect(tcp, host)?;
TlsConnector::new(), start_connect(stream, hostname), and danger_accept_invalid_certs() — named to be conspicuous at the call site, because it disables verification.
TlsAcceptor::new(cert_path: Str, key_path: Str) and start_accept(stream) for the server side.
Any binary using this module must add
sslandcryptotolink_libsin itscryoconfig. Seeffi::openssl.
A TlsStream owns both the SSL* and the underlying TcpStream, hence the descriptor. Drop order is load-bearing: SSL_shutdown sends close_notify, SSL_free releases the SSL and its context reference (the socket BIO is BIO_NOCLOSE, so the descriptor survives), and finally TcpStream::drop closes it. Because SSL_new took a reference on the context, the context outlives the stream regardless of the connector's lifetime.
HTTPS
net::https ties the pieces together for one-shot requests: resolve the hostname through net::dns, dial with net::socket::tcp, wrap and verify with net::tls, and serialize and parse with net::http. The HTTP layer never learns it is talking to TLS.
HttpsClient::new() verifies certificates; HttpsClient::insecure() does not.
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.