From 112828f548b3e67dec163acdaaa1923dbc5fa90c Mon Sep 17 00:00:00 2001 From: Arik Date: Mon, 24 Feb 2025 16:22:37 -0800 Subject: [PATCH] Proxy `/r/sat/{sat}/at/{index}` endpoint (#4022) --- src/subcommand/server.rs | 101 ++++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 1ccc556bcb..40e63e5fc6 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -79,6 +79,10 @@ struct Search { #[folder = "static"] struct StaticAssets; +lazy_static! { + static ref SAT_AT_INDEX_PATH: Regex = Regex::new(r"^/r/sat/[^/]+/at/[^/]+$").unwrap(); +} + #[derive(Debug, Parser, Clone)] pub struct Server { #[arg( @@ -273,7 +277,6 @@ impl Server { get(r::parent_inscriptions_paginated), ) .route("/r/sat/{sat_number}", get(r::sat)) - .route("/r/sat/{sat_number}/at/{index}", get(r::sat_at_index)) .route("/r/sat/{sat_number}/{page}", get(r::sat_paginated)) .route("/r/tx/{txid}", get(r::tx)) .route( @@ -291,11 +294,12 @@ impl Server { ) .route("/r/inscription/{inscription_id}", get(r::inscription)) .route("/r/metadata/{inscription_id}", get(r::metadata)) + .route("/r/sat/{sat_number}/at/{index}", get(r::sat_at_index)) .route( "/r/sat/{sat_number}/at/{index}/content", get(r::sat_at_index_content), ) - .layer(axum::middleware::from_fn(Self::proxy_fallback)); + .layer(axum::middleware::from_fn(Self::proxy_layer)); let router = router.merge(proxiable_routes); @@ -529,22 +533,37 @@ impl Server { Ok(acceptor) } - async fn proxy_fallback( - Extension(server_config): Extension>, + async fn proxy_layer( + server_config: Extension>, request: http::Request, next: axum::middleware::Next, ) -> ServerResult { - let mut path = request.uri().path().to_string(); - - path.remove(0); // remove leading slash + let path = request.uri().path().to_owned(); let response = next.run(request).await; - let status = response.status(); - if let Some(proxy) = server_config.proxy.as_ref() { - if status == StatusCode::NOT_FOUND { + if let Some(proxy) = &server_config.proxy { + if response.status() == StatusCode::NOT_FOUND { return task::block_in_place(|| Server::proxy(proxy, &path)); } + + // `/r/sat//at/` does not return a 404 when no + // inscription is present, so we must deserialize and check the body. + if SAT_AT_INDEX_PATH.is_match(&path) { + let (parts, body) = response.into_parts(); + + let bytes = axum::body::to_bytes(body, usize::MAX) + .await + .map_err(|err| anyhow!(err))?; + + if let Ok(api::SatInscription { id: None }) = + serde_json::from_slice::(&bytes) + { + return task::block_in_place(|| Server::proxy(proxy, &path)); + } + + return Ok(Response::from_parts(parts, axum::body::Body::from(bytes))); + } } Ok(response) @@ -1856,7 +1875,7 @@ impl Server { fn proxy(proxy: &Url, path: &str) -> ServerResult { let response = reqwest::blocking::Client::new() - .get(format!("{}{}", proxy, path)) + .get(format!("{}{}", proxy, &path[1..])) .send() .map_err(|err| anyhow!(err))?; @@ -7039,6 +7058,66 @@ next ); } + #[test] + fn sat_at_index_proxy() { + let server = TestServer::builder() + .index_sats() + .chain(Chain::Regtest) + .build(); + + server.mine_blocks(1); + + let inscription = Inscription { + content_type: Some("text/html".into()), + body: Some("foo".into()), + ..default() + }; + + let txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription.to_witness())], + ..default() + }); + + server.mine_blocks(1); + + let id = InscriptionId { txid, index: 0 }; + let ordinal: u64 = 5000000000; + + pretty_assert_eq!( + server.get_json::(format!("/r/sat/{ordinal}/at/-1")), + api::SatInscription { id: Some(id) } + ); + + let server_with_proxy = TestServer::builder() + .chain(Chain::Regtest) + .server_option("--proxy", server.url.as_ref()) + .build(); + let sat_indexed_server_with_proxy = TestServer::builder() + .index_sats() + .chain(Chain::Regtest) + .server_option("--proxy", server.url.as_ref()) + .build(); + + server_with_proxy.mine_blocks(1); + sat_indexed_server_with_proxy.mine_blocks(1); + + pretty_assert_eq!( + server.get_json::(format!("/r/sat/{ordinal}/at/-1")), + api::SatInscription { id: Some(id) } + ); + + pretty_assert_eq!( + server_with_proxy.get_json::(format!("/r/sat/{ordinal}/at/-1")), + api::SatInscription { id: Some(id) } + ); + + pretty_assert_eq!( + sat_indexed_server_with_proxy + .get_json::(format!("/r/sat/{ordinal}/at/-1")), + api::SatInscription { id: Some(id) } + ); + } + #[test] fn sat_at_index_content_proxy() { let server = TestServer::builder()