Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  • nodelay
  • ttl

IO macro

This section will be documented when the macro becomes stable, or removed if it is not relevant.