Skip to main content

pubky_homeserver/client_server/
app.rs

1use super::AppState;
2
3#[cfg(any(test, feature = "testing"))]
4use crate::MockDataDir;
5
6use crate::{
7    app_context::{AppContext, AppContextConversionError},
8    PersistentDataDir,
9};
10use anyhow::Result;
11use futures_util::TryFutureExt;
12use pubky_common::auth::AuthVerifier;
13use std::net::TcpListener;
14use std::path::PathBuf;
15use std::time::Duration;
16
17use axum::{
18    routing::{get, post},
19    Router,
20};
21use axum_server::{
22    tls_rustls::{RustlsAcceptor, RustlsConfig},
23    Handle,
24};
25use std::{net::SocketAddr, sync::Arc};
26use tower_cookies::CookieManagerLayer;
27use tower_http::cors::CorsLayer;
28
29use super::layers::{
30    pubky_host::PubkyHostLayer,
31    rate_limiter::{BandwidthQuotaLimitLayer, RequestRateLimitLayer},
32    trace::with_trace_layer,
33};
34use super::routes::{auth, events, root, signup_tokens, tenants};
35
36/// Errors that can occur when building a `HomeserverCore`.
37#[derive(Debug, thiserror::Error)]
38pub enum ClientServerBuildError {
39    /// Failed to run the ICANN web server.
40    #[error("ICANN web server error: {0}")]
41    IcannWebServer(anyhow::Error),
42    /// Failed to run the Pubky TLS web server.
43    #[error("Pubky TLS web server error: {0}")]
44    PubkyTlsServer(anyhow::Error),
45    /// Failed to convert the data directory to an AppContext.
46    #[error("AppContext conversion error: {0}")]
47    AppContext(#[from] AppContextConversionError),
48    /// Failed to build request-count rate limit layer.
49    #[error("Request-count rate limit configuration error: {0}")]
50    RequestRateLimits(String),
51}
52
53/// A Pubky homeserver with ICANN HTTP and Pubky TLS servers.
54pub struct ClientServer {
55    /// Keep context alive.
56    context: AppContext,
57
58    pub(crate) icann_http_handle: Handle<SocketAddr>,
59    pub(crate) icann_http_socket: SocketAddr,
60
61    pub(crate) pubky_tls_handle: Handle<SocketAddr>,
62    pub(crate) pubky_tls_socket: SocketAddr,
63}
64
65impl ClientServer {
66    /// Run the homeserver with configurations from a data directory.
67    pub async fn start_with_persistent_data_dir_path(
68        dir_path: PathBuf,
69    ) -> Result<Self, ClientServerBuildError> {
70        let data_dir = PersistentDataDir::new(dir_path);
71        let context = AppContext::read_from(data_dir).await?;
72        Self::start(context).await
73    }
74
75    /// Run the homeserver with configurations from a data directory.
76    pub async fn start_with_persistent_data_dir(
77        dir: PersistentDataDir,
78    ) -> Result<Self, ClientServerBuildError> {
79        let context = AppContext::read_from(dir).await?;
80        Self::start(context).await
81    }
82
83    /// Run the homeserver with configurations from a data directory mock.
84    #[cfg(any(test, feature = "testing"))]
85    pub async fn start_with_mock_data_dir(
86        dir: MockDataDir,
87    ) -> Result<Self, ClientServerBuildError> {
88        let context = AppContext::read_from(dir).await?;
89        Self::start(context).await
90    }
91
92    /// Start homeserver services with the given application context.
93    pub async fn start(context: AppContext) -> std::result::Result<Self, ClientServerBuildError> {
94        let router = Self::create_router(&context)?;
95
96        let (icann_http_handle, icann_http_socket) =
97            Self::start_icann_http_server(&context, router.clone())
98                .await
99                .map_err(ClientServerBuildError::IcannWebServer)?;
100        let (pubky_tls_handle, pubky_tls_socket) = Self::start_pubky_tls_server(&context, router)
101            .await
102            .map_err(ClientServerBuildError::PubkyTlsServer)?;
103
104        Ok(Self {
105            context,
106            icann_http_handle,
107            pubky_tls_handle,
108            icann_http_socket,
109            pubky_tls_socket,
110        })
111    }
112
113    pub(crate) fn create_router(
114        context: &AppContext,
115    ) -> std::result::Result<Router, ClientServerBuildError> {
116        let state = AppState {
117            verifier: AuthVerifier::default(),
118            sql_db: context.sql_db.clone(),
119            file_service: context.file_service.clone(),
120            signup_mode: context.config_toml.general.signup_mode.clone(),
121            metrics: context.metrics.clone(),
122            events_service: context.events_service.clone(),
123            user_service: context.user_service.clone(),
124            default_storage_mb: context.config_toml.storage.default_quota_mb,
125        };
126        super::create_app(state.clone(), context)
127    }
128
129    /// Start the ICANN HTTP server
130    async fn start_icann_http_server(
131        context: &AppContext,
132        router: Router,
133    ) -> Result<(Handle<SocketAddr>, SocketAddr)> {
134        // Icann http server
135        let http_listener = TcpListener::bind(context.config_toml.drive.icann_listen_socket)?;
136        http_listener.set_nonblocking(true)?;
137        let http_socket = http_listener.local_addr()?;
138        let http_handle = Handle::new();
139        let server = axum_server::from_tcp(http_listener)?;
140        tokio::spawn(
141            server
142                .handle(http_handle.clone())
143                .serve(router.into_make_service_with_connect_info::<SocketAddr>())
144                .map_err(|error| {
145                    tracing::error!(?error, "Homeserver icann http server error");
146                    println!("Homeserver icann http server error: {:?}", error);
147                }),
148        );
149
150        Ok((http_handle, http_socket))
151    }
152
153    /// Start the Pubky TLS server
154    async fn start_pubky_tls_server(
155        context: &AppContext,
156        router: Router,
157    ) -> Result<(Handle<SocketAddr>, SocketAddr)> {
158        // Pubky tls server
159        let https_listener = TcpListener::bind(context.config_toml.drive.pubky_listen_socket)?;
160        https_listener.set_nonblocking(true)?;
161        let https_socket = https_listener.local_addr()?;
162        let https_handle = Handle::new();
163        let server = axum_server::from_tcp(https_listener)?;
164        tokio::spawn(
165            server
166                .acceptor(RustlsAcceptor::new(RustlsConfig::from_config(Arc::new(
167                    context.keypair.to_rpk_rustls_server_config(),
168                ))))
169                .handle(https_handle.clone())
170                .serve(router.into_make_service_with_connect_info::<SocketAddr>())
171                .map_err(|error| {
172                    tracing::error!(?error, "Homeserver pubky tls server error");
173                    println!("Homeserver pubky tls server error: {:?}", error);
174                }),
175        );
176
177        Ok((https_handle, https_socket))
178    }
179    /// Get the URL of the icann http server.
180    pub fn icann_http_url_string(&self) -> String {
181        format!("http://{}", self.icann_http_socket)
182    }
183
184    /// Get the URL of the pubky tls server with the Pubky DNS name.
185    pub fn pubky_tls_dns_url_string(&self) -> String {
186        format!("https://{}", self.context.keypair.public_key().z32())
187    }
188
189    /// Get the URL of the pubky tls server with the Pubky IP address.
190    pub fn pubky_tls_ip_url_ring(&self) -> String {
191        format!("https://{}", self.pubky_tls_socket)
192    }
193
194    /// Shutdown the http and tls servers.
195    pub fn shutdown(&self) {
196        self.icann_http_handle
197            .graceful_shutdown(Some(Duration::from_secs(5)));
198        self.pubky_tls_handle
199            .graceful_shutdown(Some(Duration::from_secs(5)));
200    }
201}
202
203impl Drop for ClientServer {
204    fn drop(&mut self) {
205        self.shutdown();
206    }
207}
208
209fn base() -> Router<AppState> {
210    Router::new()
211        .route("/", get(root::handler))
212        .route("/signup", post(auth::signup))
213        .route("/signup_tokens/{token}", get(signup_tokens::get))
214        .route("/session", post(auth::signin))
215        // Events
216        .route("/events/", get(events::feed))
217        .route("/events-stream", get(events::feed_stream))
218
219    // TODO: add size limit
220    // TODO: revisit if we enable streaming big payloads
221    // TODO: maybe add to a separate router (drive router?).
222}
223
224pub fn create_app(
225    state: AppState,
226    context: &AppContext,
227) -> std::result::Result<Router, ClientServerBuildError> {
228    let request_rate_limit_layer =
229        RequestRateLimitLayer::from_path_limits(context.config_toml.drive.rate_limits.clone())
230            .map_err(ClientServerBuildError::RequestRateLimits)?;
231
232    let app = base()
233        .merge(tenants::router(state.clone()))
234        .layer(CorsLayer::very_permissive())
235        .layer(BandwidthQuotaLimitLayer::new(
236            context.user_service.clone(),
237            context.config_toml.default_quotas.clone(),
238        ))
239        .layer(request_rate_limit_layer)
240        .layer(CookieManagerLayer::new())
241        .layer(PubkyHostLayer)
242        .with_state(state);
243
244    // Apply trace and pubky host layers to the complete router.
245    Ok(with_trace_layer(app))
246}