112 lines
4.0 KiB
Rust
112 lines
4.0 KiB
Rust
//! Very basic example to showcase how to use iroh's APIs.
|
|||
|
|
//!
|
||
|
|
//! This example implements a simple protocol that echos any data sent to it in the first stream.
|
||
|
|
//!
|
||
|
|
//! ## Usage
|
||
|
|
//!
|
||
|
|
//! cargo run --example echo
|
||
|
|
|
||
|
|
use iroh::{
|
||
|
|
Endpoint, EndpointAddr,
|
||
|
|
endpoint::{Connection, presets},
|
||
|
|
protocol::{AcceptError, ProtocolHandler, Router},
|
||
|
|
};
|
||
|
|
use n0_error::{Result, StdResultExt};
|
||
|
|
|
||
|
|
/// Each protocol is identified by its ALPN string.
|
||
|
|
///
|
||
|
|
/// The ALPN, or application-layer protocol negotiation, is exchanged in the connection handshake,
|
||
|
|
/// and the connection is aborted unless both endpoints pass the same bytestring.
|
||
|
|
const ALPN: &[u8] = b"iroh-example/echo/0";
|
||
|
|
|
||
|
|
#[tokio::main]
|
||
|
|
async fn main() -> Result<()> {
|
||
|
|
tracing_subscriber::fmt::init();
|
||
|
|
let router = start_accept_side().await?;
|
||
|
|
|
||
|
|
// wait for the endpoint to be online
|
||
|
|
router.endpoint().online().await;
|
||
|
|
|
||
|
|
connect_side(router.endpoint().addr()).await?;
|
||
|
|
|
||
|
|
// This makes sure the endpoint in the router is closed properly and connections close gracefully
|
||
|
|
router.shutdown().await.anyerr()?;
|
||
|
|
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
async fn connect_side(addr: EndpointAddr) -> Result<()> {
|
||
|
|
let endpoint = Endpoint::bind(presets::N0).await?;
|
||
|
|
|
||
|
|
// Open a connection to the accepting endpoint
|
||
|
|
let conn = endpoint.connect(addr, ALPN).await?;
|
||
|
|
|
||
|
|
// Open a bidirectional QUIC stream
|
||
|
|
let (mut send, mut recv) = conn.open_bi().await.anyerr()?;
|
||
|
|
|
||
|
|
// Send some data to be echoed
|
||
|
|
send.write_all(b"Hello, world!").await.anyerr()?;
|
||
|
|
|
||
|
|
// Signal the end of data for this particular stream
|
||
|
|
send.finish().anyerr()?;
|
||
|
|
|
||
|
|
// Receive the echo, but limit reading up to maximum 1000 bytes
|
||
|
|
let response = recv.read_to_end(1000).await.anyerr()?;
|
||
|
|
assert_eq!(&response, b"Hello, world!");
|
||
|
|
|
||
|
|
// Explicitly close the whole connection.
|
||
|
|
conn.close(0u32.into(), b"bye!");
|
||
|
|
|
||
|
|
// The above call only queues a close message to be sent (see how it's not async!).
|
||
|
|
// We need to actually call this to make sure this message is sent out.
|
||
|
|
endpoint.close().await;
|
||
|
|
// If we don't call this, but continue using the endpoint, we then the queued
|
||
|
|
// close call will eventually be picked up and sent.
|
||
|
|
// But always try to wait for endpoint.close().await to go through before dropping
|
||
|
|
// the endpoint to ensure any queued messages are sent through and connections are
|
||
|
|
// closed gracefully.
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
async fn start_accept_side() -> Result<Router> {
|
||
|
|
let endpoint = Endpoint::bind(presets::N0).await?;
|
||
|
|
|
||
|
|
// Build our protocol handler and add our protocol, identified by its ALPN, and spawn the endpoint.
|
||
|
|
let router = Router::builder(endpoint).accept(ALPN, Echo).spawn();
|
||
|
|
|
||
|
|
Ok(router)
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone)]
|
||
|
|
struct Echo;
|
||
|
|
|
||
|
|
impl ProtocolHandler for Echo {
|
||
|
|
/// The `accept` method is called for each incoming connection for our ALPN.
|
||
|
|
///
|
||
|
|
/// The returned future runs on a newly spawned tokio task, so it can run as long as
|
||
|
|
/// the connection lasts.
|
||
|
|
async fn accept(&self, connection: Connection) -> Result<(), AcceptError> {
|
||
|
|
// We can get the remote's endpoint id from the connection.
|
||
|
|
let endpoint_id = connection.remote_id();
|
||
|
|
println!("accepted connection from {endpoint_id}");
|
||
|
|
|
||
|
|
// Our protocol is a simple request-response protocol, so we expect the
|
||
|
|
// connecting peer to open a single bi-directional stream.
|
||
|
|
let (mut send, mut recv) = connection.accept_bi().await?;
|
||
|
|
|
||
|
|
// Echo any bytes received back directly.
|
||
|
|
// This will keep copying until the sender signals the end of data on the stream.
|
||
|
|
let bytes_sent = tokio::io::copy(&mut recv, &mut send).await?;
|
||
|
|
println!("Copied over {bytes_sent} byte(s)");
|
||
|
|
|
||
|
|
// By calling `finish` on the send stream we signal that we will not send anything
|
||
|
|
// further, which makes the receive stream on the other end terminate.
|
||
|
|
send.finish()?;
|
||
|
|
|
||
|
|
// Wait until the remote closes the connection, which it does once it
|
||
|
|
// received the response.
|
||
|
|
connection.closed().await;
|
||
|
|
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
}
|