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