std/net¶
TCP networking: connect a client stream, bind a listener and accept
connections, read and write bytes, resolve DNS names, and probe whether an
address is reachable. The handle types (TcpStream, TcpListener) and the
free functions all return values through the Result<T, Error> model.
import std/net { connect }
fun main() {
match connect("example.com:80") {
Ok(stream) -> {
stream.close()
print("connected")
}
Err(e) -> print(e.message),
}
}
Importing¶
Select the free functions you use. The methods on TcpStream and
TcpListener arrive with the types, so they need no separate selector.
A socket cannot cross the FFI boundary, so the runtime keeps the real socket
in a process-wide registry and hands Raven an opaque integer id. TcpStream
and TcpListener are thin structs wrapping that id; every method looks the
socket up by id and acts on it.
You rarely touch id directly. Build the handles with connect, listen,
and accept, and use the methods below.
The error model¶
Every fallible operation returns Result<T, Error> where Error is the
std/error type tagged with kind "net". The message is a short
context prefix (the operation name) joined to the OS error text. Match on the
Result, or use the std/error combinators to thread it.
import std/net { connect }
fun main() {
match connect("127.0.0.1:9999") {
Ok(stream) -> stream.close(),
Err(e) -> print(e.message), // e.g. connect: ...
}
}
The one exception is reachable, which never fails and returns a plain
Bool rather than a Result.
Connecting a client¶
connect(addr: String) -> Result<TcpStream, Error>¶
Open a TCP stream to addr in "host:port" form. On success you get an open
TcpStream; on failure an Err carrying the OS message.
import std/net { connect }
fun main() {
match connect("example.com:80") {
Ok(stream) -> {
print("connected")
stream.close()
}
Err(e) -> print(e.message),
}
}
Reading and writing a stream¶
The stream methods come in with the TcpStream type.
read(self, max: Int) -> Result<String, Error>¶
Read up to max bytes and return them as a String. The payload is a byte
buffer carried in a String (lossy UTF-8 at the FFI boundary), not
guaranteed text. A clean end of stream returns Ok(""), so an empty result
means EOF rather than an error.
read_all(self) -> Result<String, Error>¶
Read until end of stream, accumulating every byte into one String. It reads
in 4 KiB chunks and stops at a clean EOF. Like read, the payload is a byte
buffer carried in a String, not guaranteed text.
import std/net { connect }
fun main() {
match connect("example.com:80") {
Ok(stream) -> {
stream.write("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
match stream.read_all() {
Ok(body) -> print(body),
Err(e) -> print(e.message),
}
stream.close()
}
Err(e) -> print(e.message),
}
}
write(self, data: String) -> Result<Int, Error>¶
Write all bytes of data and return the count written.
set_read_timeout_ms(self, ms: Int)¶
Set the read timeout in milliseconds. A value of 0 means no timeout
(blocking reads); a positive value makes a stalled read fail rather than hang.
This method does not return a Result.
close(self)¶
Close the stream, releasing the runtime-side socket. It does not return a
Result.
import std/net { connect }
fun main() {
match connect("example.com:80") {
Ok(stream) -> {
stream.set_read_timeout_ms(2000)
match stream.write("GET / HTTP/1.0\r\n\r\n") {
Ok(n) -> print("sent ${n} bytes"),
Err(e) -> print(e.message),
}
match stream.read(1024) {
Ok(reply) -> print(reply),
Err(e) -> print(e.message),
}
stream.close()
}
Err(e) -> print(e.message),
}
}
Binding a listener¶
listen(addr: String) -> Result<TcpListener, Error>¶
Bind a TCP listener to addr in "host:port" form.
accept(self) -> Result<TcpStream, Error>¶
A method on TcpListener. Block until a connection arrives and return the
accepted TcpStream. The block is intentional: Raven v2 has no concurrency,
so accept waits rather than polling.
A server loops: bind once with listen, then call accept repeatedly,
serving each accepted stream before accepting the next.
import std/net { listen }
fun serve() {
match listen("127.0.0.1:7878") {
Ok(server) -> {
while true {
match server.accept() {
Ok(client) -> {
match client.read(1024) {
Ok(req) -> {
client.write("hello\n")
}
Err(e) -> print(e.message),
}
client.close()
}
Err(e) -> print(e.message),
}
}
}
Err(e) -> print(e.message),
}
}
Because v2 has no threads, a single program is a client or a server, not both at once.
Resolving and probing¶
dns_lookup(host: String) -> Result<List<String>, Error>¶
Resolve host to its IP addresses as a List<String>. An empty result is an
empty list, not a one-element list of "".
import std/net { dns_lookup }
fun main() {
match dns_lookup("example.com") {
Ok(addrs) -> {
let i = 0
while i < addrs.len() {
print(addrs[i])
i = i + 1
}
}
Err(e) -> print(e.message),
}
}
reachable(addr: String) -> Bool¶
Probe whether addr in "host:port" form accepts a TCP connection within a
short timeout. This is a non-failing check: it returns a plain Bool and
never sets an error.
import std/net { reachable }
fun main() {
if reachable("127.0.0.1:7878") {
print("up")
} else {
print("down")
}
}
Worked example: a one-shot client¶
Connect, send a request, read the reply, and close, handling the Result at
each step.
import std/net { connect }
fun fetch(addr: String, request: String) -> Result<String, Error> {
let stream = connect(addr)?
stream.set_read_timeout_ms(3000)
stream.write(request)?
let reply = stream.read(4096)?
stream.close()
return Ok(reply)
}
fun main() {
match fetch("example.com:80", "GET / HTTP/1.0\r\n\r\n") {
Ok(body) -> print(body),
Err(e) -> print(e.message),
}
}
See also¶
- std/http builds request and response handling on top of TCP.
- std/error for the
Errortype,error_kind, and the?operator used above. - std/string for slicing and scanning the byte buffers that
readreturns.