I/O
Since ProSA is designed to connect to external systems, helpers for I/O operation are essential.
This page covers I/O from server and client perspectives, with a few examples.
Listener
StreamListener type is used to handle server sockets.
It can be instantiated from a ListenerSetting using the bind method.
It supports three types of server sockets:
- UNIX
- TCP
- SSL
Once the object is created, you must call the accept method in a loop to accept client connections.
Each accepted connection will create a Stream socket, which can be managed just like a client socket.
accept negotiates SSL before it returns, so the loop carries the handshake of every client. A
handshake lasts up to the configured ssl_timeout, which is long enough for a slow client to keep
the loop from accepting anyone else. An SSL server should instead accept with accept_raw, take an
owned snapshot of the SSL parameters with handshaker(), and handshake in a spawned task:
let (stream, addr) = listener.accept_raw().await?;
// An OpenSSL context is reference counted, so the snapshot costs an atomic increment
let handshaker = listener.handshaker().cloned();
tokio::spawn(async move {
let stream = match handshaker {
Some(handshaker) => handshaker.handshake(stream).await?,
None => stream,
};
// ... serve the client
});
The snapshot is taken when the client is accepted, so a certificate rotated meanwhile applies to the clients accepted afterwards and leaves the handshakes in flight alone.
Sharing the listener
set_handshaker takes the listener by value, so a listener that is shared — held in an Arc by the
accept loop and by whatever else drives it — cannot be replaced to rotate its certificate. Bind it
with bind_raw instead: it binds the socket and hands the SSL parameters back beside it rather than
inside it, so the handshaker you hold is the only copy and rotating it is an assignment.
let (listener, mut handshaker) = listener_setting.bind_raw().await?;
let listener = Arc::new(listener);
// Accept loop, same shape, from the handshaker held next to the listener
let (stream, addr) = listener.accept_raw().await?;
let client_handshaker = handshaker.clone();
tokio::spawn(async move { /* ... */ });
// Configuration reload or certificate-source change notification
handshaker = listener_setting.build_handshaker().await?;
A listener bound this way carries no SSL parameters of its own: it formats as tcp://, its
handshaker() is None, and accept returns a plain TCP stream. That is what keeps a rotation
honest — there is no copy left inside the listener for accept, handshake or Debug to serve
after you have replaced yours.
Stream
A Stream represents a client socket or a socket created by a StreamListener when a client connects.
It can be instantiated from a TargetSetting using the connect method.
It supports several types of client sockets:
- UNIX
- TCP
- SSL
- TCP over HTTP proxy
- SSL over HTTP proxy
You can manually connect using the connect_* methods as appropriate.
After creating the socket, you have several options to further configure it, such as:
nodelayttl
IO macro
This section will be documented when the macro becomes stable, or removed if it is not relevant.