Skip to content

Commit 14ae108

Browse files
committed
feat: add connection info request guard and logging setup for tests
1 parent 8541a80 commit 14ae108

File tree

2 files changed

+139
-1
lines changed

2 files changed

+139
-1
lines changed

src/visualization/server.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ use base64::Engine;
5757
use include_dir::{include_dir, Dir};
5858
use rocket::fairing::{Fairing, Info, Kind};
5959
use rocket::figment::Figment;
60+
use rocket::http::uri::{Host, Origin};
6061
use rocket::http::{ContentType, Header, HeaderMap};
6162
use rocket::request::FromRequest;
6263
use rocket::response::{Redirect, Responder};
@@ -66,6 +67,7 @@ use rocket_okapi::{openapi, openapi_get_routes, rapidoc::*, settings::UrlObject}
6667
use std::env;
6768
use std::fmt::Debug;
6869
use std::io::Cursor;
70+
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
6971
use std::ops::Deref;
7072
use 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,

tests/rs256_jwt_test.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,22 @@ use rocket::http::{ContentType, Status};
1313
use rsa::pkcs1::{EncodeRsaPrivateKey, EncodeRsaPublicKey};
1414
use rust_photoacoustic::visualization::jwt::JwtIssuer;
1515
use rust_photoacoustic::visualization::jwt_keys::JwkKeySet;
16-
use serde::de;
1716
use serde_json::Value;
17+
use std::sync::Once;
1818
use std::time::{SystemTime, UNIX_EPOCH};
1919

20+
static INIT: Once = Once::new();
21+
22+
/// Setup logger for tests
23+
fn setup() {
24+
INIT.call_once(|| {
25+
env_logger::builder()
26+
.filter_level(log::LevelFilter::Debug)
27+
.is_test(true)
28+
.init();
29+
});
30+
}
31+
2032
/// Generate a test configuration for Rocket
2133
fn get_test_figment() -> rocket::figment::Figment {
2234
rocket::Config::figment()
@@ -148,6 +160,8 @@ fn test_rs256_jwt_token_generation_and_validation() {
148160

149161
#[rocket::async_test]
150162
async fn test_oidc_endpoints_with_rs256() {
163+
// Initialize the logger
164+
setup();
151165
// Generate test RS256 key pair
152166
let (_, _, private_base64, public_base64) = generate_test_rs256_keys();
153167

0 commit comments

Comments
 (0)