Skip to content

Commit c7975bf

Browse files
authored
Merge pull request #3 from lsst-dm/tickets/DM-52788
DM-52788: Update server to provide relative links to href instead of full
2 parents 3fe5312 + f5294a3 commit c7975bf

1 file changed

Lines changed: 52 additions & 14 deletions

File tree

src/main.rs

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use google_cloud_storage::http::objects::list::ListObjectsRequest;
55
use log::{LevelFilter, error, info};
66
use std::convert::Infallible;
77
use std::env;
8+
use std::net::SocketAddr;
89
use time::format_description;
910
use warp::http::StatusCode;
1011
use warp::{Filter, http::Response};
@@ -27,17 +28,33 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2728

2829
let client_filter = warp::any().map(move || client.clone());
2930
let bucket_filter = warp::any().map(move || bucket_name.clone());
31+
// Filter to get the client's IP address (SocketAddr)
32+
let remote_addr_filter = warp::addr::remote();
33+
34+
// Filter to get the User-Agent header
35+
let user_agent_filter = warp::header::optional("user-agent");
3036

3137
let routes = warp::path::full()
38+
.and(remote_addr_filter)
39+
.and(user_agent_filter)
3240
.and(client_filter)
3341
.and(bucket_filter)
3442
.and_then(serve_gcs_content);
3543

36-
warp::serve(routes).run(([0, 0, 0, 0], 8585)).await;
44+
warp::serve(routes).run(([0, 0, 0, 0], 8080)).await;
3745

3846
Ok(())
3947
}
4048

49+
fn is_bot(user_agent: &str) -> bool {
50+
let ua = user_agent.to_lowercase();
51+
ua.contains("bot")
52+
|| ua.contains("spider")
53+
|| ua.contains("scrap")
54+
|| ua.contains("googlebot")
55+
|| ua.contains("bingbot")
56+
|| ua.contains("http client")
57+
}
4158
async fn check_for_dirs(
4259
client: &Client,
4360
bucket: String,
@@ -74,18 +91,21 @@ async fn check_for_dirs(
7491
}
7592
}
7693
None
77-
// client
78-
// .list_objects(&list_req_test_prefix)
79-
// .await
80-
// .is_ok_and(|res| res.prefixes.is_some() || res.items.is_some())
8194
}
8295

8396
async fn serve_gcs_content(
8497
path: warp::path::FullPath,
98+
remote_addr: Option<SocketAddr>,
99+
user_agent: Option<String>,
85100
client: Client,
86101
bucket_name: String,
87102
) -> Result<impl warp::Reply, Infallible> {
88103
let path_str = path.as_str().trim_start_matches('/').to_string();
104+
let client_ip = remote_addr
105+
.map(|addr| addr.ip().to_string())
106+
.unwrap_or_else(|| "unknown".to_string());
107+
let user_agent = user_agent.unwrap_or_else(|| "none".to_string());
108+
let is_scrapper = is_bot(user_agent.as_str());
89109

90110
// The GCS API is a flat hierarchy. A "folder" is just an object prefix.
91111
let is_dir = path_str.ends_with('/');
@@ -110,7 +130,10 @@ async fn serve_gcs_content(
110130
object: path_str.clone(),
111131
..Default::default()
112132
};
113-
info!("DOWNLOAD_REQUEST: path='{}'", path_str);
133+
info!(
134+
"DOWNLOAD_REQUEST: path='{}', user-agent='{}', ip='{}', scrapper={}",
135+
path_str, user_agent, client_ip, is_scrapper
136+
);
114137

115138
let response = match client.download_object(&request, &Range::default()).await {
116139
Ok(data) => {
@@ -130,10 +153,13 @@ async fn serve_gcs_content(
130153
"attachment"
131154
};
132155
info!(
133-
"DOWNLOAD_SUCCESS: file='{}', size={} bytes, disposition='{}'",
156+
"DOWNLOAD_SUCCESS: file='{}', size={} bytes, disposition='{}', user-agent='{}', ip='{}', scrapper={}",
134157
path_str,
135158
data.len(),
136-
disposition
159+
disposition,
160+
user_agent,
161+
client_ip,
162+
is_scrapper
137163
);
138164
Response::builder()
139165
.header(
@@ -144,7 +170,10 @@ async fn serve_gcs_content(
144170
.unwrap()
145171
}
146172
Err(e) => {
147-
error!("DOWNLOAD_ERROR: file='{}', error='{:?}'", path_str, e);
173+
error!(
174+
"DOWNLOAD_ERROR: file='{}', user-agent='{}', ip='{}', scrapper={}, error='{:?}'",
175+
path_str, user_agent, client_ip, is_scrapper, e
176+
);
148177
Response::builder()
149178
.status(404)
150179
.body("File not found".as_bytes().to_vec())
@@ -162,7 +191,10 @@ async fn serve_gcs_content(
162191
Some(format!("{}/", path_str.trim_end_matches('/')))
163192
};
164193

165-
info!("LISTING_REQUEST: path='{}'", path_str);
194+
info!(
195+
"LISTING_REQUEST: path='{}', user-agent='{}', ip='{}', scrapper={}",
196+
path_str, user_agent, client_ip, is_scrapper
197+
);
166198
let mut folders = Vec::new();
167199
let mut files = Vec::new();
168200
let mut next_page_token: Option<String> = None;
@@ -223,11 +255,14 @@ async fn serve_gcs_content(
223255
next_page_token = objects.next_page_token;
224256
}
225257
info!(
226-
"LISTING_SUCCESS: path='{}', folders={}, files={}, total={}",
258+
"LISTING_SUCCESS: path='{}', folders={}, files={}, total={}, user-agent='{}', ip='{}', scrapper={}",
227259
path_str,
228260
folders.len(),
229261
files.len(),
230-
folders.len() + files.len()
262+
folders.len() + files.len(),
263+
user_agent,
264+
client_ip,
265+
is_scrapper
231266
);
232267

233268
let html = build_html(path_str, folders, files);
@@ -244,6 +279,7 @@ async fn serve_gcs_content(
244279
.body("Not Found".as_bytes().to_vec())
245280
.unwrap())
246281
}
282+
247283
fn build_html(
248284
path_str: String,
249285
folders: Vec<String>,
@@ -317,8 +353,10 @@ fn build_html(
317353
files
318354
.iter()
319355
.map(|(name, size, updated)| format!(
320-
r#"<tr><td><i class="fa-solid fa-file"></i></td> <td><a href="/{}">{}</a></td><td align="right">{}</td><td align="right">{}</td></tr>"#,
321-
name,
356+
r#"<tr><td><i class="fa-solid fa-file"></i></td> <td><a href="{}">{}</a></td><td align="right">{}</td><td align="right">{}</td></tr>"#,
357+
// **CHANGE: Use only the file name for the relative HREF attribute**
358+
name.split('/').next_back().unwrap_or(""),
359+
// Use only the file name for the display text
322360
name.split('/').next_back().unwrap_or(""),
323361
size,
324362
updated

0 commit comments

Comments
 (0)