Crate service_binding

Source
Expand description

§service-binding

CI Crates.io

Provides a way for servers and clients to describe their service bindings and client endpoints in a structured format.

This crate automates parsing and binding to TCP sockets, Unix sockets and Windows Named Pipes.

By design this crate is very lean and mostly relies on what is in std (with an exception of macOS launchd service binding).

The URI scheme bindings have been heavily inspired by how Docker Engine specifies them.

use service_binding::{Binding, Listener};

let host = "tcp://127.0.0.1:8080"; // or "unix:///tmp/socket"

// parse the URI into a Binding
let binding: Binding = host.parse().unwrap();

// convert the binding into a Listener
match binding.try_into().unwrap() {
    #[cfg(unix)]
    Listener::Unix(listener) => {
        // bind to a unix domain socket
    },
    Listener::Tcp(listener) => {
        // bind to a TCP socket
    }
    Listener::NamedPipe(pipe) => {
        // bind to a Windows Named Pipe
    }
}

§Supported schemes

URI formatExampleDescriptionBindingListener / Stream
tcp://ip:porttcp://127.0.0.1:8080TCP IP addressSocketsTcp
tcp://address:porttcp://localhost:8080Hostname with address resolution 1SocketsTcp
unix://pathunix:///run/user/1000/test.sockUnix domain sockets 2FilePathUnix
fd://fd://systemd first socket activation 34FileDescriptorUnix
fd://<number>fd://3exact number file descriptorFileDescriptorUnix
fd://<socket-name>fd://httpsocket activation by name 4FileDescriptorUnix
npipe://<name>npipe://agentWindows Named Pipe 5NamedPipeNamedPipe

§Example

The following example uses clap and actix-web and makes it possible to run the server using any combination of Unix domain sockets (including systemd socket activation) and regular TCP socket bound to a TCP port:

use actix_web::{web, App, HttpServer, Responder};
use clap::Parser;
use service_binding::{Binding, Listener};

#[derive(Parser, Debug)]
struct Args {
    #[clap(
        env = "HOST",
        short = 'H',
        long,
        default_value = "tcp://127.0.0.1:8080"
    )]
    host: Binding,
}

async fn greet() -> impl Responder {
    "Hello!"
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let server = HttpServer::new(move || {
        App::new().route("/", web::get().to(greet))
    });

    match Args::parse().host.try_into()? {
        #[cfg(unix)]
        Listener::Unix(listener) => server.listen_uds(listener),
        Listener::Tcp(listener) => server.listen(listener),
        _ => Err(std::io::Error::other("Unsupported listener type")),
    }?.run().await
}

§systemd Socket Activation

This crate also supports systemd’s Socket Activation. If the argument to be parsed is fd:// the Listener object returned will be a Unix variant containing the listener provided by systemd.

For example the following file defines a socket unit: ~/.config/systemd/user/app.socket:

[Socket]
ListenStream=%t/app.sock
FileDescriptorName=service-name

[Install]
WantedBy=sockets.target

When enabled it will create a new socket file in $XDG_RUNTIME_DIR directory. When this socket is connected to systemd will start the service; fd:// reads the correct systemd environment variable and returns the Unix domain socket.

The service unit file ~/.config/systemd/user/app.service:

[Service]
ExecStart=/usr/bin/app -H fd://

Since the socket is named (FileDescriptorName=service-name) it can also be selected using its explicit name: fd://service-name.

§launchd Socket Activation

On macOS launchd socket activation is also available although the socket needs to be explicitly named through the fd://socket-name syntax.

The corresponding plist file (which can be placed in ~/Library/LaunchAgents and loaded via launchctl load ~/Library/LaunchAgents/service.plist):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>EnvironmentVariables</key>
	<dict>
		<key>RUST_LOG</key>
		<string>debug</string>
	</dict>
	<key>KeepAlive</key>
	<true/>
	<key>Label</key>
	<string>com.example.service</string>
	<key>OnDemand</key>
	<true/>
	<key>ProgramArguments</key>
	<array>
		<string>/path/to/service</string>
		<string>-H</string>
		<string>fd://socket-name</string> <!-- activate socket by name -->
	</array>
	<key>RunAtLoad</key>
	<true/>
	<key>Sockets</key>
	<dict>
		<key>socket-name</key> <!-- the socket name here -->
		<dict>
			<key>SockPathName</key>
			<string>/path/to/socket</string>
			<key>SockFamily</key>
			<string>Unix</string>
		</dict>
	</dict>
	<key>StandardErrorPath</key>
	<string>/Users/test/Library/Logs/service/stderr.log</string>
	<key>StandardOutPath</key>
	<string>/Users/test/Library/Logs/service/stdout.log</string>
</dict>
</plist>

§License

This project is licensed under either of:

at your option.

§Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.


  1. binds to the first address that succeeds (see TcpListener::bind

  2. not available on Windows through the std right now (see #271 and #56533

  3. equivalent of fd://3 but fails if more sockets have been passed 

  4. listener only 

  5. translates to \\.\pipe\test 

Enums§

Binding
Service binding.
Error
Errors while processing service listeners.
Listener
Opened service listener.
Stream
Client service connection.