-
Notifications
You must be signed in to change notification settings - Fork 2
Closed
Labels
enhancementNew feature or requestNew feature or request
Description
Problem
The current middleware system operates at the Protocol/JSON-RPC layer, which misses HTTP-specific concerns like:
- Header injection (auth tokens, correlation IDs, custom headers)
- Request/response logging with header inclusion
- Status code-based conditionals (retry on 429, circuit break on 503)
- HTTP-specific transformations before reaching Protocol layer
The TypeScript SDK provides fetch-wrapping middleware for these concerns. We need equivalent ergonomics.
Proposed Solution
Add a transport-specific HttpMiddleware trait that operates inside HttpTransport and StreamableHttpTransport before sending to hyper/reqwest:
#[async_trait]
pub trait HttpMiddleware: Send + Sync {
/// Called before HTTP request is sent
async fn on_request(
&self,
request: &mut http::Request<Vec<u8>>,
context: &HttpMiddlewareContext,
) -> Result<()>;
/// Called after HTTP response is received
async fn on_response(
&self,
response: &mut http::Response<Vec<u8>>,
context: &HttpMiddlewareContext,
) -> Result<()>;
/// Priority for ordering (lower runs first)
fn priority(&self) -> MiddlewarePriority {
MiddlewarePriority::Normal
}
/// Should this middleware execute for this request?
async fn should_execute(&self, context: &HttpMiddlewareContext) -> bool {
true
}
}
pub struct HttpMiddlewareContext {
pub url: Url,
pub method: http::Method,
pub attempt: u32,
pub metadata: Arc<RwLock<HashMap<String, String>>>,
}Integration points:
HttpTransport::send()- Apply middleware chain before/after reqwest callStreamableHttpTransport- Apply before POST requestsHttpTransportConfig::middleware_chain- Configure middleware on transport creation
Use Cases
1. Header Injection
struct CustomHeaderMiddleware {
headers: HashMap<String, String>,
}
#[async_trait]
impl HttpMiddleware for CustomHeaderMiddleware {
async fn on_request(
&self,
request: &mut http::Request<Vec<u8>>,
_context: &HttpMiddlewareContext,
) -> Result<()> {
for (key, value) in &self.headers {
request.headers_mut().insert(
HeaderName::from_str(key)?,
HeaderValue::from_str(value)?
);
}
Ok(())
}
}
// Usage
let transport = HttpTransport::builder()
.url(server_url)
.middleware(CustomHeaderMiddleware::new([
("X-API-Key", api_key),
("X-Request-ID", uuid),
]))
.build()?;2. Request/Response Logging with Headers
struct HttpLoggingMiddleware {
include_headers: bool,
log_body: bool,
}
#[async_trait]
impl HttpMiddleware for HttpLoggingMiddleware {
async fn on_request(&self, req: &mut http::Request<Vec<u8>>, ctx: &HttpMiddlewareContext) -> Result<()> {
if self.include_headers {
tracing::info!(
method = %ctx.method,
url = %ctx.url,
headers = ?req.headers(),
"HTTP request"
);
}
Ok(())
}
async fn on_response(&self, res: &mut http::Response<Vec<u8>>, ctx: &HttpMiddlewareContext) -> Result<()> {
tracing::info!(
status = %res.status(),
headers = ?res.headers(),
"HTTP response"
);
Ok(())
}
}3. Status Code-Based Actions
struct StatusCodeMiddleware;
#[async_trait]
impl HttpMiddleware for StatusCodeMiddleware {
async fn on_response(&self, res: &mut http::Response<Vec<u8>>, ctx: &HttpMiddlewareContext) -> Result<()> {
match res.status() {
StatusCode::TOO_MANY_REQUESTS => {
if let Some(retry_after) = res.headers().get("Retry-After") {
ctx.set_metadata("retry_after", retry_after.to_str()?);
}
Err(Error::RateLimited("Rate limit exceeded".into()))
}
StatusCode::SERVICE_UNAVAILABLE => {
Err(Error::Transport("Service unavailable".into()))
}
_ => Ok(())
}
}
}Implementation Plan
-
Phase 1: Core trait and context
- Define
HttpMiddlewaretrait - Add
HttpMiddlewareContextwith url, method, attempt - Add
HttpMiddlewareChainfor ordering
- Define
-
Phase 2: Integration with transports
- Update
HttpTransport::send()to apply middleware - Update
StreamableHttpTransportrequest handling - Add
middleware()builder method to configs
- Update
-
Phase 3: Built-in middleware
HeaderInjectionMiddleware- Add custom headersHttpLoggingMiddleware- Request/response logging with headersCorrelationIdMiddleware- Inject/propagate correlation IDsStatusCodeHandlerMiddleware- Handle specific status codes
-
Phase 4: Documentation and examples
- Add chapter to pmcp-book on HTTP middleware
- Example:
examples/30_http_middleware.rs - Update transport documentation
Benefits
- Separation of concerns: HTTP-level vs Protocol-level middleware
- TypeScript parity: Matches TS SDK's fetch-wrapping ergonomics
- Composability: Chain multiple HTTP concerns
- Type safety: Rust's http crate types prevent header/status errors
- Foundation: Enables OAuth middleware (Issue feat: Add HttpMiddleware trait for transport-level HTTP concerns #82)
References
- TypeScript SDK:
typescript-sdk/src/client/transports/http.ts(fetch wrapping) - Existing Protocol middleware:
src/shared/middleware.rs - Issue feat: Add first-class middleware integration to Client/Protocol/Transport #80: First-class middleware integration
- Issue feat: Add HttpMiddleware trait for transport-level HTTP concerns #82: OAuth client middleware (depends on this)
Related Issues
- feat: Add first-class middleware integration to Client/Protocol/Transport #80 - First-class middleware integration API
- feat: Add HttpMiddleware trait for transport-level HTTP concerns #82 - OAuth client-side middleware (depends on HttpMiddleware)
Priority: High
Complexity: Medium
Dependencies: None (builds on existing middleware foundation)
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request