This document describes the RequestContext refactor that improves the RequestHandler trait interface by consolidating request metadata into a unified context object.
Before v0.2.37 the RequestHandler trait had a method signature that accepted one parameters for the DNS message and non for the client address. This design had limitations in ratelimit extensibility, so we refactored it to add a client_addr parameter. However, as we continued to add more metadata (e.g., protocol type for DoH/DoT/DoQ), it became clear that a more scalable solution was needed.
async fn handle(&self, request: Message, client_addr: Option<SocketAddr>) -> Result<Message>;
This design had several limitations:
Option parameters made the API harder to understandThe refactored interface uses a unified RequestContext struct:
async fn handle(&self, ctx: RequestContext) -> Result<Message>;
pub struct RequestContext {
pub message: Message,
pub client_info: Option<ClientInfo>,
pub protocol: Protocol,
}
Methods:
new(message, protocol) - Create context without client informationwith_client(message, client_addr, protocol) - Create context with client informationclient_ip() - Get client IP address (if available)client_addr() - Get client socket address (if available)into_message() - Consume context and extract the messageinto_raw() - Consume context and extract all componentspub struct ClientInfo {
pub addr: SocketAddr,
pub ip: IpAddr,
pub port: u16,
}
Automatically extracted from SocketAddr for convenience.
pub enum Protocol {
Udp,
Tcp,
DoH, // DNS over HTTPS
DoT, // DNS over TLS
DoQ, // DNS over QUIC
}
Identifies which protocol was used for the request.
Adding new metadata is now straightforward without breaking existing code:
// Future: Add TLS session info, HTTP headers, etc.
pub struct RequestContext {
pub message: Message,
pub client_info: Option<ClientInfo>,
pub protocol: Protocol,
pub tls_info: Option<TlsInfo>, // Easy to add
}
The context object makes it explicit that all request metadata is grouped together:
// Before: unclear what the parameters mean
handler.handle(message, Some(addr)).await?
// After: clear that we're passing a request context
let ctx = RequestContext::with_client(message, Some(addr), Protocol::Udp);
handler.handle(ctx).await?
The Protocol enum ensures protocol types are well-defined and prevents errors:
// Type-safe protocol identification
match ctx.protocol {
Protocol::DoH => { /* DoH-specific handling */ }
Protocol::Udp | Protocol::Tcp => { /* Standard DNS handling */ }
_ => { /* ... */ }
}
Each server implementation creates the appropriate Protocol value:
Protocol::UdpProtocol::TcpProtocol::DoH (note: client IP may not be reliable due to proxies)Protocol::DoTProtocol::DoQThe PluginHandler automatically extracts client information and adds it to plugin metadata:
impl RequestHandler for PluginHandler {
async fn handle(&self, ctx: RequestContext) -> Result<Message> {
// Extract client info from context
if let Some(client_info) = ctx.client_info {
context.set_metadata("client_ip", client_info.ip.to_string());
context.set_metadata("client_port", client_info.port.to_string());
}
// ...
}
}
This ensures plugins can access client information through the metadata system.
For background tasks (e.g., cache refresh), create a context without client information:
// Background refresh - no client information needed
let ctx = RequestContext::new(request, Protocol::Udp);
handler.handle(ctx).await?;
src/server/handler.rs - Added RequestContext, ClientInfo, Protocol types; updated traitsrc/server/mod.rs - Exported new typessrc/plugin/mod.rs - Updated PluginHandler to use RequestContextsrc/server/udp.rs - Updated UDP serversrc/server/tcp.rs - Updated TCP serversrc/server/doh.rs - Updated DoH server (GET and POST handlers)src/server/dot.rs - Updated DoT serversrc/server/doq.rs - Updated DoQ serversrc/plugins/server.rs - Updated PluginRequestHandlersrc/plugins/cache.rs - Updated background refresh handlerstests/server_test.rs - Updated test handlerstests/integration_doq.rs - Updated test handlerstests/integration_tls_doh_dot.rs - Updated test handlersAll existing tests pass without modification to test logic, demonstrating backward compatibility:
$ cargo test --lib
test result: ok. 428 passed; 0 failed; 10 ignored
$ cargo test --tests
test result: ok. All integration tests passed
New unit tests verify RequestContext functionality:
test_request_context_with_client - Context with client informationtest_request_context_without_client - Context without client informationThe new design enables several future improvements: