QuickDID is a high-performance AT Protocol identity resolution service written in Rust. It provides handle-to-DID resolution with Redis-backed caching and queue processing.
at main 152 lines 4.6 kB view raw
1use crate::handle_resolver::HandleResolver; 2use crate::metrics::SharedMetricsPublisher; 3use crate::queue::{HandleResolutionWork, QueueAdapter}; 4use atproto_lexicon::resolve::LexiconResolver; 5use axum::{ 6 Router, 7 extract::{MatchedPath, State}, 8 http::Request, 9 middleware::{self, Next}, 10 response::{Json, Response}, 11 routing::get, 12}; 13use serde_json::json; 14use std::sync::Arc; 15use std::time::Instant; 16use tower_http::services::ServeDir; 17 18pub(crate) struct InnerAppContext { 19 pub(crate) handle_resolver: Arc<dyn HandleResolver>, 20 pub(crate) handle_queue: Arc<dyn QueueAdapter<HandleResolutionWork>>, 21 pub(crate) lexicon_resolver: Arc<dyn LexiconResolver>, 22 pub(crate) metrics: SharedMetricsPublisher, 23 pub(crate) etag_seed: String, 24 pub(crate) cache_control_header: Option<String>, 25 pub(crate) static_files_dir: String, 26} 27 28#[derive(Clone)] 29pub struct AppContext(pub(crate) Arc<InnerAppContext>); 30 31impl AppContext { 32 /// Create a new AppContext with the provided configuration. 33 pub fn new( 34 handle_resolver: Arc<dyn HandleResolver>, 35 handle_queue: Arc<dyn QueueAdapter<HandleResolutionWork>>, 36 lexicon_resolver: Arc<dyn LexiconResolver>, 37 metrics: SharedMetricsPublisher, 38 etag_seed: String, 39 cache_control_header: Option<String>, 40 static_files_dir: String, 41 ) -> Self { 42 Self(Arc::new(InnerAppContext { 43 handle_resolver, 44 handle_queue, 45 lexicon_resolver, 46 metrics, 47 etag_seed, 48 cache_control_header, 49 static_files_dir, 50 })) 51 } 52 53 // Internal accessor methods for handlers 54 pub(super) fn etag_seed(&self) -> &str { 55 &self.0.etag_seed 56 } 57 58 pub(super) fn cache_control_header(&self) -> Option<&str> { 59 self.0.cache_control_header.as_deref() 60 } 61 62 pub(super) fn static_files_dir(&self) -> &str { 63 &self.0.static_files_dir 64 } 65} 66 67use axum::extract::FromRef; 68 69macro_rules! impl_from_ref { 70 ($context:ty, $field:ident, $type:ty) => { 71 impl FromRef<$context> for $type { 72 fn from_ref(context: &$context) -> Self { 73 context.0.$field.clone() 74 } 75 } 76 }; 77} 78 79impl_from_ref!(AppContext, handle_resolver, Arc<dyn HandleResolver>); 80impl_from_ref!( 81 AppContext, 82 handle_queue, 83 Arc<dyn QueueAdapter<HandleResolutionWork>> 84); 85impl_from_ref!(AppContext, lexicon_resolver, Arc<dyn LexiconResolver>); 86impl_from_ref!(AppContext, metrics, SharedMetricsPublisher); 87 88/// Middleware to track HTTP request metrics 89async fn metrics_middleware( 90 State(metrics): State<SharedMetricsPublisher>, 91 matched_path: Option<MatchedPath>, 92 request: Request<axum::body::Body>, 93 next: Next, 94) -> Response { 95 let start = Instant::now(); 96 let method = request.method().to_string(); 97 let path = matched_path 98 .as_ref() 99 .map(|p| p.as_str().to_string()) 100 .unwrap_or_else(|| "unknown".to_string()); 101 102 // Process the request 103 let response = next.run(request).await; 104 105 // Calculate duration 106 let duration_ms = start.elapsed().as_millis() as u64; 107 let status_code = response.status().as_u16().to_string(); 108 109 // Publish metrics with tags 110 metrics 111 .time_with_tags( 112 "http.request.duration_ms", 113 duration_ms, 114 &[ 115 ("method", &method), 116 ("path", &path), 117 ("status", &status_code), 118 ], 119 ) 120 .await; 121 122 response 123} 124 125pub fn create_router(app_context: AppContext) -> Router { 126 let static_dir = app_context.static_files_dir().to_string(); 127 128 Router::new() 129 .route("/xrpc/_health", get(handle_xrpc_health)) 130 .route( 131 "/xrpc/com.atproto.identity.resolveHandle", 132 get(super::handle_xrpc_resolve_handle::handle_xrpc_resolve_handle) 133 .options(super::handle_xrpc_resolve_handle::handle_xrpc_resolve_handle_options), 134 ) 135 .route( 136 "/xrpc/com.atproto.lexicon.resolveLexicon", 137 get(super::handle_xrpc_resolve_lexicon::handle_xrpc_resolve_lexicon) 138 .options(super::handle_xrpc_resolve_lexicon::handle_xrpc_resolve_lexicon_options), 139 ) 140 .fallback_service(ServeDir::new(static_dir)) 141 .layer(middleware::from_fn_with_state( 142 app_context.0.metrics.clone(), 143 metrics_middleware, 144 )) 145 .with_state(app_context) 146} 147 148pub(super) async fn handle_xrpc_health() -> Json<serde_json::Value> { 149 Json(json!({ 150 "version": "0.1.0", 151 })) 152}