1
use anyhow::Result;
2
use http_body_util::{BodyExt, Empty};
3
use hyper::body::Bytes;
4
use hyper::http::uri::Scheme;
5
use hyper::{Request, Uri};
6
use hyper_util::rt::TokioIo;
7
use tokio::io::{AsyncRead, AsyncWrite};
8
use tokio_native_tls::native_tls::TlsConnector;
9

            
10
use arti_client::{TorClient, TorClientConfig};
11

            
12
#[tokio::main]
13
async fn main() -> Result<()> {
14
    // Arti uses the `tracing` crate for logging. Install a handler for this, to print Arti's logs.
15
    // (You'll need to set RUST_LOG=info as an environment variable to actually see much; also try
16
    // =debug for more detailed logging.)
17
    tracing_subscriber::fmt::init();
18

            
19
    // You can run this example with any arbitrary HTTP/1.1 (raw or within TLS) URL, but we'll default to icanhazip
20
    // because it's a good way of demonstrating that the connection is via Tor.
21
    let url: Uri = std::env::args()
22
        .nth(1)
23
        .unwrap_or_else(|| "https://icanhazip.com".into())
24
        .parse()?;
25
    let host = url.host().unwrap();
26
    let https = url.scheme() == Some(&Scheme::HTTPS);
27

            
28
    eprintln!("starting Arti...");
29

            
30
    // The client config includes things like where to store persistent Tor network state.
31
    // The defaults provided are the same as the Arti standalone application, and save data
32
    // to a conventional place depending on operating system (for example, ~/.local/share/arti
33
    // on Linux platforms)
34
    let config = TorClientConfig::default();
35

            
36
    // We now let the Arti client start and bootstrap a connection to the network.
37
    // (This takes a while to gather the necessary consensus state, etc.)
38
    let tor_client = TorClient::create_bootstrapped(config).await?;
39

            
40
    let port = match url.port_u16() {
41
        Some(port) => port,
42
        _ if https => 443,
43
        _ => 80,
44
    };
45

            
46
    let stream = tor_client.connect((host, port)).await?;
47

            
48
    // The rest is just standard usage of Hyper.
49
    eprintln!("requesting {} via Tor...", url);
50

            
51
    if https {
52
        let cx = TlsConnector::builder().build()?;
53
        let cx = tokio_native_tls::TlsConnector::from(cx);
54
        let stream = cx.connect(host, stream).await?;
55
        make_request(host, stream).await
56
    } else {
57
        make_request(host, stream).await
58
    }
59
}
60

            
61
async fn make_request(
62
    host: &str,
63
    stream: impl AsyncRead + AsyncWrite + Unpin + Send + 'static,
64
) -> Result<()> {
65
    let (mut request_sender, connection) =
66
        hyper::client::conn::http1::handshake(TokioIo::new(stream)).await?;
67

            
68
    // spawn a task to poll the connection and drive the HTTP state
69
    tokio::spawn(async move {
70
        connection.await.unwrap();
71
    });
72

            
73
    let mut resp = request_sender
74
        .send_request(
75
            Request::builder()
76
                .header("Host", host)
77
                .method("GET")
78
                .body(Empty::<Bytes>::new())?,
79
        )
80
        .await?;
81

            
82
    eprintln!("status: {}", resp.status());
83

            
84
    while let Some(frame) = resp.body_mut().frame().await {
85
        let bytes = frame?.into_data().unwrap();
86
        eprintln!("body: {}", std::str::from_utf8(&bytes)?);
87
    }
88

            
89
    Ok(())
90
}