hyper_http_hs_example/
main.rs

1use std::sync::Arc;
2
3use anyhow::Result;
4use futures::StreamExt;
5use hyper::body::Incoming;
6use hyper::server::conn::http1;
7use hyper::service::service_fn;
8use hyper::{Request, Response, StatusCode};
9use hyper_util::rt::TokioIo;
10use tokio_util::sync::CancellationToken;
11
12use arti_client::{TorClient, TorClientConfig};
13use safelog::sensitive;
14use tor_cell::relaycell::msg::Connected;
15use tor_hsservice::config::OnionServiceConfigBuilder;
16use tor_hsservice::StreamRequest;
17use tor_proto::stream::IncomingStreamRequest;
18
19struct WebHandler {
20    shutdown: CancellationToken,
21}
22
23impl WebHandler {
24    async fn serve(&self, request: Request<Incoming>) -> Result<Response<String>> {
25        let path = request.uri().path();
26
27        // TODO: Unauthenticated management. This route is accessible by anyone, and exists solely
28        //  to demonstrate how to safely shutdown further incoming requests. You should probably
29        //  move this elsewhere to ensure proper checks are in place!
30        if path == "/shutdown" {
31            self.shutdown.cancel();
32        }
33
34        Ok(Response::builder().status(StatusCode::OK).body(format!(
35            "{} {}",
36            request.method(),
37            path
38        ))?)
39    }
40}
41
42#[tokio::main]
43async fn main() {
44    // Make sure you read doc/OnionService.md to extract your Onion service hostname
45
46    // Arti uses the `tracing` crate for logging. Install a handler for this, to print Arti's logs.
47    // (You'll need to set RUST_LOG=info as an environment variable to actually see much; also try
48    // =debug for more detailed logging.)
49    tracing_subscriber::fmt::init();
50
51    // Initialize web server data, if you need to
52    let handler = Arc::new(WebHandler {
53        shutdown: CancellationToken::new(),
54    });
55
56    // The client config includes things like where to store persistent Tor network state.
57    // The defaults provided are the same as the Arti standalone application, and save data
58    // to a conventional place depending on operating system (for example, ~/.local/share/arti
59    // on Linux platforms)
60    let config = TorClientConfig::default();
61
62    // We now let the Arti client start and bootstrap a connection to the network.
63    // (This takes a while to gather the necessary consensus state, etc.)
64    let client = TorClient::create_bootstrapped(config).await.unwrap();
65
66    let svc_cfg = OnionServiceConfigBuilder::default()
67        .nickname("allium-ampeloprasum".parse().unwrap())
68        .build()
69        .unwrap();
70    let (service, request_stream) = client.launch_onion_service(svc_cfg).unwrap();
71
72    // Wait until the service is believed to be fully reachable.
73    eprintln!("waiting for service to become fully reachable");
74    while let Some(status) = service.status_events().next().await {
75        if status.state().is_fully_reachable() {
76            break;
77        }
78    }
79    eprintln!(
80        "ready to serve connections via {}",
81        service.onion_address().unwrap()
82    );
83
84    let stream_requests = tor_hsservice::handle_rend_requests(request_stream)
85        .take_until(handler.shutdown.cancelled());
86    tokio::pin!(stream_requests);
87
88    while let Some(stream_request) = stream_requests.next().await {
89        // incoming connection
90        let handler = handler.clone();
91
92        tokio::spawn(async move {
93            let request = stream_request.request().clone();
94            let result = handle_stream_request(stream_request, handler).await;
95
96            match result {
97                Ok(()) => {}
98                Err(err) => {
99                    eprintln!("error serving connection {:?}: {}", sensitive(request), err);
100                }
101            }
102        });
103    }
104
105    drop(service);
106    eprintln!("onion service exited cleanly");
107}
108
109async fn handle_stream_request(
110    stream_request: StreamRequest,
111    handler: Arc<WebHandler>,
112) -> Result<()> {
113    match stream_request.request() {
114        IncomingStreamRequest::Begin(begin) if begin.port() == 80 => {
115            let onion_service_stream = stream_request.accept(Connected::new_empty()).await?;
116            let io = TokioIo::new(onion_service_stream);
117
118            http1::Builder::new()
119                .serve_connection(io, service_fn(|request| handler.serve(request)))
120                .await?;
121        }
122        _ => {
123            stream_request.shutdown_circuit()?;
124        }
125    }
126
127    Ok(())
128}