@@ -57,6 +57,7 @@ use base64::Engine;
5757use include_dir:: { include_dir, Dir } ;
5858use rocket:: fairing:: { Fairing , Info , Kind } ;
5959use rocket:: figment:: Figment ;
60+ use rocket:: http:: uri:: { Host , Origin } ;
6061use rocket:: http:: { ContentType , Header , HeaderMap } ;
6162use rocket:: request:: FromRequest ;
6263use rocket:: response:: { Redirect , Responder } ;
@@ -66,6 +67,7 @@ use rocket_okapi::{openapi, openapi_get_routes, rapidoc::*, settings::UrlObject}
6667use std:: env;
6768use std:: fmt:: Debug ;
6869use std:: io:: Cursor ;
70+ use std:: net:: { IpAddr , Ipv4Addr , SocketAddr } ;
6971use std:: ops:: Deref ;
7072use std:: path:: PathBuf ;
7173
@@ -180,6 +182,128 @@ impl<'r> Debug for Headers<'r> {
180182 }
181183}
182184
185+ /// Request guard for accessing detailed connection information from the client
186+ ///
187+ /// This struct provides comprehensive information about the incoming HTTP connection,
188+ /// including host, IP addresses, URL structure, and connection scheme (HTTP/HTTPS).
189+ /// It can be used in route handlers to obtain details about how a client is connecting
190+ /// to the server, which is useful for logging, analytics, and generating absolute URLs.
191+ ///
192+ /// # Fields
193+ ///
194+ /// * `host_port` - The host and port as a string (e.g., "example.com:8080")
195+ /// * `origin` - The normalized URI origin from the request
196+ /// * `ip` - The client's IP address, or 127.0.0.1 if unavailable
197+ /// * `real_ip` - The client's real IP address from X-Forwarded-For header if available
198+ /// * `remote` - The client's socket address if available
199+ /// * `scheme` - The URL scheme ("http" or "https")
200+ /// * `base_url_with_port` - The base URL including the port (e.g., "https://example.com:8080")
201+ /// * `base_url` - The base URL without the port if standard (e.g., "https://example.com")
202+ ///
203+ /// # Usage in Routes
204+ ///
205+ /// ```
206+ /// use rocket::get;
207+ /// use rust_photoacoustic::visualization::server::ConnectionInfo;
208+ ///
209+ /// #[get("/connection-info")]
210+ /// fn show_connection_info(conn_info: ConnectionInfo<'_>) -> String {
211+ /// format!(
212+ /// "Connected via: {}\nYour IP: {}\nBase URL: {}",
213+ /// conn_info.scheme, conn_info.ip, conn_info.base_url
214+ /// )
215+ /// }
216+ /// ```
217+ ///
218+ /// # Security Considerations
219+ ///
220+ /// This struct provides information that could be useful for logging and debugging,
221+ /// but care should be taken when exposing client IP addresses or other connection
222+ /// details in responses, as this could have privacy implications. Additionally, in
223+ /// production environments with reverse proxies, ensure proper configuration of
224+ /// the X-Forwarded-For and related headers for accurate client IP detection.
225+ pub struct ConnectionInfo < ' r > {
226+ pub host_port : String ,
227+ pub origin : Origin < ' r > ,
228+ pub ip : IpAddr ,
229+ pub real_ip : Option < IpAddr > ,
230+ pub remote : Option < SocketAddr > ,
231+ pub scheme : String ,
232+ pub base_url_with_port : String ,
233+ pub base_url : String ,
234+ }
235+ /// Request guard for accessing connection information
236+ #[ rocket:: async_trait]
237+ impl < ' r > FromRequest < ' r > for ConnectionInfo < ' r > {
238+ type Error = ( ) ;
239+
240+ /// Extracts connection information from the request
241+ ///
242+ /// This implementation provides access to the host, port, scheme,
243+ /// and path of the incoming request.
244+ /// NOTE: if the host is not set in the request, it will use localhost:8080 hardcoded
245+ ///
246+ /// # Parameters
247+ ///
248+ /// * `req` - The incoming HTTP request
249+ ///
250+ /// # Returns
251+ ///
252+ /// A successful outcome containing the connection information
253+ async fn from_request ( req : & ' r Request < ' _ > ) -> rocket:: request:: Outcome < Self , Self :: Error > {
254+ let default_host_string = env:: var ( "HOST" ) . unwrap_or_else ( |_| "localhost:8080" . to_string ( ) ) ;
255+ let default_host = Host :: parse ( default_host_string. as_str ( ) ) . expect ( "valid host" ) ;
256+ let host_port = req. host ( ) . unwrap_or ( & default_host) ;
257+ let port = host_port. port ( ) . unwrap_or ( 80 ) ;
258+ let host: & str = host_port. domain ( ) . as_str ( ) ;
259+ let origin = req. uri ( ) . to_owned ( ) . into_normalized ( ) ;
260+ let ip = req
261+ . client_ip ( )
262+ . unwrap_or ( Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) . into ( ) ) ;
263+ let real_ip = req. real_ip ( ) ;
264+ let remote = req. remote ( ) ;
265+ let scheme = if req. rocket ( ) . config ( ) . tls_enabled ( ) {
266+ "https" . to_string ( )
267+ } else {
268+ "http" . to_string ( )
269+ } ;
270+ let base_url_with_port = format ! ( "{}://{}" , scheme, host_port) ;
271+ let base_url = if port == 80 || port == 443 {
272+ format ! ( "{}://{}" , scheme, host)
273+ } else {
274+ format ! ( "{}://{}:{}" , scheme, host, port)
275+ } ;
276+ rocket:: request:: Outcome :: Success ( ConnectionInfo {
277+ host_port : host_port. to_string ( ) ,
278+ origin,
279+ ip,
280+ real_ip,
281+ remote,
282+ scheme,
283+ base_url_with_port,
284+ base_url,
285+ } )
286+ }
287+ }
288+
289+ impl < ' r > Debug for ConnectionInfo < ' r > {
290+ /// Formats the ConnectionInfo for debug output
291+ ///
292+ /// This implementation allows the ConnectionInfo struct to be used with
293+ /// debug formatting macros like `println!("{:?}", connection_info)`.
294+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
295+ f. debug_tuple ( "ConnectionInfo" )
296+ . field ( & self . host_port )
297+ . field ( & self . origin )
298+ . field ( & self . ip )
299+ . field ( & self . real_ip )
300+ . field ( & self . remote )
301+ . field ( & self . scheme )
302+ . field ( & self . base_url )
303+ . field ( & self . base_url_with_port )
304+ . finish ( )
305+ }
306+ }
183307/// Cross-Origin Resource Sharing (CORS) fairing for Rocket
184308///
185309/// This fairing adds CORS headers to all responses from the server,
0 commit comments