1use std::pin::Pin;
34use std::sync::atomic::{AtomicBool, Ordering};
35use std::sync::{Arc, Mutex};
36use std::task::{Context, Poll};
37
38use axum::body::Body;
39use axum::extract::{Request, State};
40use axum::middleware::Next;
41use axum::response::{IntoResponse, Response};
42use bytes::Bytes;
43use common_memory_manager::MemoryGuard;
44use http::StatusCode;
45use http_body::{Body as HttpBody, Frame};
46
47use crate::error::Result;
48use crate::request_memory_limiter::ServerMemoryLimiter;
49use crate::request_memory_metrics::RequestMemoryMetrics;
50
51#[derive(Clone, Copy)]
56pub(crate) struct ContentEncoded;
57
58pub async fn memory_limit_middleware(
59 State(limiter): State<ServerMemoryLimiter>,
60 req: Request,
61 next: Next,
62) -> Response {
63 let content_length = req
64 .headers()
65 .get(http::header::CONTENT_LENGTH)
66 .and_then(|v| v.to_str().ok())
67 .and_then(|v| v.parse::<u64>().ok())
68 .unwrap_or(0);
69
70 let guard = match limiter.acquire(content_length).await {
71 Ok(guard) => guard,
72 Err(e) => {
73 return (
74 StatusCode::TOO_MANY_REQUESTS,
75 format!("Request body memory limit exceeded: {}", e),
76 )
77 .into_response();
78 }
79 };
80
81 let content_encoded = req
82 .headers()
83 .get(http::header::CONTENT_ENCODING)
84 .and_then(|v| v.to_str().ok())
85 .is_some_and(|v| !v.eq_ignore_ascii_case("identity"));
86
87 let accounting = BodyMemoryAccounting::default();
90 accounting.hold(guard);
91 let retained_accounting = accounting.clone();
96 let (mut parts, body) = req.into_parts();
97 parts.extensions.insert(limiter.clone());
100 parts.extensions.insert(accounting.clone());
101 if content_encoded {
102 parts.extensions.insert(ContentEncoded);
103 }
104 let accounted = AccountedBody::new(body, limiter, content_length, accounting);
105 let req = Request::from_parts(parts, Body::new(accounted));
106
107 let response = next.run(req).await;
108 quota_exceeded_response(&retained_accounting, response)
111}
112
113fn quota_exceeded_response(accounting: &BodyMemoryAccounting, response: Response) -> Response {
119 if accounting.take_quota_exceeded() {
122 return (
123 StatusCode::TOO_MANY_REQUESTS,
124 "Request body memory limit exceeded",
125 )
126 .into_response();
127 }
128 response
129}
130
131pub async fn decoded_body_accounting_middleware(
143 State(limiter): State<ServerMemoryLimiter>,
144 req: Request,
145 next: Next,
146) -> Response {
147 if req.extensions().get::<ContentEncoded>().is_none() {
148 return next.run(req).await;
149 }
150
151 let content_length = req
152 .headers()
153 .get(http::header::CONTENT_LENGTH)
154 .and_then(|v| v.to_str().ok())
155 .and_then(|v| v.parse::<u64>().ok())
156 .unwrap_or(0);
157
158 let accounting = req
160 .extensions()
161 .get::<BodyMemoryAccounting>()
162 .cloned()
163 .unwrap_or_default();
164 let retained_accounting = accounting.clone();
166 let (mut parts, body) = req.into_parts();
167 parts.extensions.insert(accounting.clone());
168 let accounted = AccountedBody::new(body, limiter, content_length, accounting);
169 let req = Request::from_parts(parts, Body::new(accounted));
170
171 let response = next.run(req).await;
172 quota_exceeded_response(&retained_accounting, response)
175}
176
177#[derive(Clone, Default)]
183pub(crate) struct BodyMemoryAccounting {
184 guards: Arc<Mutex<Vec<MemoryGuard<RequestMemoryMetrics>>>>,
185 quota_exceeded: Arc<AtomicBool>,
188}
189
190impl BodyMemoryAccounting {
191 pub(crate) fn reserved_bytes(&self) -> u64 {
193 self.guards
194 .lock()
195 .unwrap()
196 .iter()
197 .map(MemoryGuard::granted_bytes)
198 .sum()
199 }
200
201 fn hold(&self, guard: MemoryGuard<RequestMemoryMetrics>) {
202 self.guards.lock().unwrap().push(guard);
203 }
204
205 fn mark_quota_exceeded(&self) {
206 self.quota_exceeded.store(true, Ordering::Release);
207 }
208
209 fn take_quota_exceeded(&self) -> bool {
210 self.quota_exceeded.swap(false, Ordering::AcqRel)
211 }
212}
213
214type AcquireFuture =
215 Pin<Box<dyn std::future::Future<Output = Result<MemoryGuard<RequestMemoryMetrics>>> + Send>>;
216
217enum ChargeOutcome {
218 Charged,
219 Pending,
220 Failed,
221}
222
223struct AccountedBody {
226 inner: Body,
227 limiter: ServerMemoryLimiter,
228 accounting: BodyMemoryAccounting,
229 streamed: u64,
231 incrementally_charged: u64,
233 pre_charged: u64,
235 pending: Option<(Frame<Bytes>, u64)>,
238 pending_acquire: Option<AcquireFuture>,
239}
240
241impl AccountedBody {
242 fn new(
243 inner: Body,
244 limiter: ServerMemoryLimiter,
245 pre_charged: u64,
246 accounting: BodyMemoryAccounting,
247 ) -> Self {
248 Self {
249 inner,
250 limiter,
251 accounting,
252 streamed: 0,
253 incrementally_charged: 0,
254 pre_charged,
255 pending: None,
256 pending_acquire: None,
257 }
258 }
259
260 fn uncharged(&self) -> u64 {
262 self.streamed
263 .saturating_sub(self.pre_charged)
264 .saturating_sub(self.incrementally_charged)
265 }
266
267 fn charge(&mut self, bytes: u64, cx: &mut Context<'_>) -> ChargeOutcome {
275 debug_assert!(bytes > 0);
276 if let Some(fut) = self.pending_acquire.as_mut() {
277 return match fut.as_mut().poll(cx) {
278 Poll::Ready(Ok(guard)) => {
279 self.accounting.hold(guard);
280 self.pending_acquire = None;
281 self.incrementally_charged += bytes;
282 ChargeOutcome::Charged
283 }
284 Poll::Ready(Err(_)) => {
285 self.pending_acquire = None;
286 ChargeOutcome::Failed
287 }
288 Poll::Pending => ChargeOutcome::Pending,
289 };
290 }
291 if let Some(guard) = self.limiter.try_acquire(bytes) {
293 self.accounting.hold(guard);
294 self.incrementally_charged += bytes;
295 return ChargeOutcome::Charged;
296 }
297 let limiter = self.limiter.clone();
298 let mut fut = Box::pin(async move { limiter.acquire(bytes).await });
299 match fut.as_mut().poll(cx) {
302 Poll::Ready(Ok(guard)) => {
303 self.accounting.hold(guard);
304 self.incrementally_charged += bytes;
305 ChargeOutcome::Charged
306 }
307 Poll::Ready(Err(_)) => ChargeOutcome::Failed,
308 Poll::Pending => {
309 self.pending_acquire = Some(fut);
310 ChargeOutcome::Pending
311 }
312 }
313 }
314
315 fn hand_out(
318 &mut self,
319 frame: Frame<Bytes>,
320 bytes: u64,
321 cx: &mut Context<'_>,
322 ) -> Poll<Option<Result<Frame<Bytes>, axum::Error>>> {
323 if bytes == 0 {
324 return Poll::Ready(Some(Ok(frame)));
325 }
326 match self.charge(bytes, cx) {
327 ChargeOutcome::Charged => Poll::Ready(Some(Ok(frame))),
328 ChargeOutcome::Pending => {
329 self.pending = Some((frame, bytes));
330 Poll::Pending
331 }
332 ChargeOutcome::Failed => {
333 self.accounting.mark_quota_exceeded();
334 Poll::Ready(Some(Err(limit_exceeded_error())))
335 }
336 }
337 }
338}
339
340impl HttpBody for AccountedBody {
341 type Data = Bytes;
342 type Error = axum::Error;
343
344 fn poll_frame(
345 self: Pin<&mut Self>,
346 cx: &mut Context<'_>,
347 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
348 let this = self.get_mut();
349
350 if let Some((frame, bytes)) = this.pending.take() {
352 return this.hand_out(frame, bytes, cx);
353 }
354
355 match Pin::new(&mut this.inner).poll_frame(cx) {
356 Poll::Pending => Poll::Pending,
357 Poll::Ready(None) => Poll::Ready(None),
358 Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
359 Poll::Ready(Some(Ok(frame))) => {
360 if let Some(data) = frame.data_ref() {
361 this.streamed += data.len() as u64;
362 let uncharged = this.uncharged();
363 this.hand_out(frame, uncharged, cx)
364 } else {
365 Poll::Ready(Some(Ok(frame)))
367 }
368 }
369 }
370 }
371
372 fn is_end_stream(&self) -> bool {
373 self.pending.is_none() && self.inner.is_end_stream()
374 }
375
376 fn size_hint(&self) -> http_body::SizeHint {
377 self.inner.size_hint()
378 }
379}
380
381fn limit_exceeded_error() -> axum::Error {
382 axum::Error::new(std::io::Error::other(
383 "request body exceeded the aggregate memory limit",
384 ))
385}
386
387#[cfg(test)]
388mod tests {
389 use axum::body::{Body, Bytes};
390 use axum::http::{Request, StatusCode, header};
391 use axum::routing::post;
392 use axum::{Router, middleware};
393 use common_memory_manager::OnExhaustedPolicy;
394 use tower::ServiceExt;
395
396 use super::memory_limit_middleware;
397 use crate::request_memory_limiter::ServerMemoryLimiter;
398
399 fn chunked_request(uri: &str, body: Vec<u8>, chunk_size: usize) -> Request<Body> {
402 let stream = futures_util::stream::iter(
403 body.chunks(chunk_size)
404 .map(|c| Ok::<_, std::io::Error>(Bytes::copy_from_slice(c)))
405 .collect::<Vec<_>>(),
406 );
407 Request::builder()
408 .uri(uri)
409 .method("POST")
410 .body(Body::from_stream(stream))
411 .unwrap()
412 }
413
414 fn counted_body_app(limiter: ServerMemoryLimiter) -> Router {
415 Router::new()
416 .route(
417 "/echo",
418 post(|body: Bytes| async move { (StatusCode::OK, body.len().to_string()) }),
419 )
420 .layer(middleware::from_fn_with_state(
421 limiter,
422 memory_limit_middleware,
423 ))
424 }
425
426 #[tokio::test]
427 async fn test_chunked_body_larger_than_quota_is_rejected() {
428 let limiter = ServerMemoryLimiter::new(8 * 1024, OnExhaustedPolicy::Fail);
430 let app = counted_body_app(limiter.clone());
431
432 let res = app
433 .oneshot(chunked_request("/echo", vec![b'x'; 64 * 1024], 1024))
434 .await
435 .unwrap();
436 assert_eq!(
437 res.status(),
438 StatusCode::TOO_MANY_REQUESTS,
439 "a chunked body larger than the quota must be rejected with 429, not 400"
440 );
441 assert_eq!(0, limiter.used_bytes(), "guards must be released");
442 }
443
444 #[tokio::test]
445 async fn test_small_chunked_body_fits() {
446 let limiter = ServerMemoryLimiter::new(8 * 1024, OnExhaustedPolicy::Fail);
447 let app = counted_body_app(limiter.clone());
448
449 let res = app
450 .oneshot(chunked_request("/echo", vec![b'x'; 1024], 512))
451 .await
452 .unwrap();
453 assert_eq!(res.status(), StatusCode::OK);
454 let body = axum::body::to_bytes(res.into_body(), 1024).await.unwrap();
455 assert_eq!(&body[..], b"1024");
456 assert_eq!(0, limiter.used_bytes(), "guards must be released");
457 }
458
459 #[tokio::test]
460 async fn test_content_length_still_admitted_upfront() {
461 let limiter = ServerMemoryLimiter::new(1024, OnExhaustedPolicy::Fail);
464 let app = counted_body_app(limiter);
465
466 let req = Request::builder()
467 .uri("/echo")
468 .method("POST")
469 .header(header::CONTENT_LENGTH, "4096")
470 .body(Body::from(vec![b'x'; 4096]))
471 .unwrap();
472 let res = app.oneshot(req).await.unwrap();
473 assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS);
474 }
475
476 #[tokio::test]
477 async fn test_understated_content_length_is_charged_the_difference() {
478 let limiter = ServerMemoryLimiter::new(2 * 1024, OnExhaustedPolicy::Fail);
481 let app = counted_body_app(limiter.clone());
482
483 let stream = futures_util::stream::iter(vec![Ok::<_, std::io::Error>(
484 Bytes::copy_from_slice(&vec![b'x'; 16 * 1024]),
485 )]);
486 let req = Request::builder()
487 .uri("/echo")
488 .method("POST")
489 .header(header::CONTENT_LENGTH, "1024")
490 .body(Body::from_stream(stream))
491 .unwrap();
492 let res = app.oneshot(req).await.unwrap();
493 assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS);
494 assert_eq!(0, limiter.used_bytes());
495 }
496
497 #[tokio::test]
498 async fn test_honest_content_length_is_not_double_charged() {
499 let limiter = ServerMemoryLimiter::new(8 * 1024, OnExhaustedPolicy::Fail);
502 let app = counted_body_app(limiter.clone());
503
504 let req = Request::builder()
505 .uri("/echo")
506 .method("POST")
507 .header(header::CONTENT_LENGTH, "4096")
508 .body(Body::from(vec![b'x'; 4096]))
509 .unwrap();
510 let res = app.oneshot(req).await.unwrap();
511 assert_eq!(res.status(), StatusCode::OK);
512 assert_eq!(0, limiter.used_bytes());
513 }
514
515 fn decompressing_app(limiter: ServerMemoryLimiter) -> Router {
518 use tower_http::decompression::RequestDecompressionLayer;
519
520 Router::new()
521 .route(
522 "/echo",
523 post(|body: Bytes| async move { (StatusCode::OK, body.len().to_string()) }),
524 )
525 .layer(middleware::from_fn_with_state(
526 limiter,
527 super::decoded_body_accounting_middleware,
528 ))
529 .layer(RequestDecompressionLayer::new().pass_through_unaccepted(true))
530 }
531
532 fn zstd_body(decoded: &[u8]) -> Body {
533 let compressed = zstd::stream::encode_all(decoded, 3).unwrap();
534 Body::from(compressed)
535 }
536
537 #[tokio::test]
538 async fn test_decompressed_body_is_charged_for_content_encoded_requests() {
539 let limiter = ServerMemoryLimiter::new(16 * 1024, OnExhaustedPolicy::Fail);
542 let app = decompressing_app(limiter.clone());
543
544 let req = Request::builder()
545 .uri("/echo")
546 .method("POST")
547 .header("content-encoding", "zstd")
548 .extension(super::ContentEncoded)
549 .body(zstd_body(&vec![b'x'; 64 * 1024]))
550 .unwrap();
551 let res = app.oneshot(req).await.unwrap();
552 assert_eq!(
553 res.status(),
554 StatusCode::TOO_MANY_REQUESTS,
555 "decoded body larger than the quota must be rejected with 429"
556 );
557 assert_eq!(0, limiter.used_bytes(), "guards must be released");
558 }
559
560 #[tokio::test]
561 async fn test_small_decompressed_body_fits() {
562 let limiter = ServerMemoryLimiter::new(16 * 1024, OnExhaustedPolicy::Fail);
563 let app = decompressing_app(limiter.clone());
564
565 let req = Request::builder()
566 .uri("/echo")
567 .method("POST")
568 .header("content-encoding", "zstd")
569 .extension(super::ContentEncoded)
570 .body(zstd_body(&vec![b'x'; 1024]))
571 .unwrap();
572 let res = app.oneshot(req).await.unwrap();
573 assert_eq!(res.status(), StatusCode::OK);
574 let body = axum::body::to_bytes(res.into_body(), 1024).await.unwrap();
575 assert_eq!(&body[..], b"1024", "the handler must see the decoded body");
576 assert_eq!(0, limiter.used_bytes(), "guards must be released");
577 }
578
579 #[tokio::test]
580 async fn test_pending_acquisition_is_attributed_to_its_own_bytes() {
581 use std::time::Duration;
582
583 use tokio_stream::wrappers::ReceiverStream;
584
585 let limiter = ServerMemoryLimiter::new(
596 8 * 1024,
597 OnExhaustedPolicy::Wait {
598 timeout: Duration::from_millis(500),
599 },
600 );
601 let app = counted_body_app(limiter.clone());
602
603 let (tx, rx) = tokio::sync::mpsc::channel::<Result<Bytes, std::io::Error>>(2);
604 let driver_limiter = limiter.clone();
605 let driver = tokio::spawn(async move {
606 let external = driver_limiter.acquire(8 * 1024).await.unwrap();
608 tx.send(Ok(Bytes::from(vec![b'a'; 4096]))).await.unwrap();
609 tokio::time::sleep(Duration::from_millis(300)).await;
610 drop(external);
612 tokio::time::sleep(Duration::from_millis(300)).await;
613 tx.send(Ok(Bytes::from(vec![b'b'; 8192]))).await.unwrap();
614 tokio::time::sleep(Duration::from_millis(1500)).await;
616 });
617
618 let res = app
619 .oneshot(
620 Request::builder()
621 .uri("/echo")
622 .method("POST")
623 .body(Body::from_stream(ReceiverStream::new(rx)))
624 .unwrap(),
625 )
626 .await
627 .unwrap();
628 driver.await.unwrap();
629
630 assert_eq!(
631 res.status(),
632 StatusCode::TOO_MANY_REQUESTS,
633 "frame B must wait for its own 8 KiB and time out, not borrow A's reservation"
634 );
635 }
636
637 #[tokio::test]
638 async fn test_plain_requests_are_not_charged_by_the_decoded_layer() {
639 let limiter = ServerMemoryLimiter::new(16 * 1024, OnExhaustedPolicy::Fail);
642 let app = decompressing_app(limiter.clone());
643
644 let req = Request::builder()
646 .uri("/echo")
647 .method("POST")
648 .body(Body::from(vec![b'x'; 1024]))
649 .unwrap();
650 let res = app.clone().oneshot(req).await.unwrap();
651 assert_eq!(res.status(), StatusCode::OK);
652
653 let req = Request::builder()
656 .uri("/echo")
657 .method("POST")
658 .header("content-encoding", "zstd")
659 .body(zstd_body(&vec![b'x'; 1024]))
660 .unwrap();
661 let res = app.oneshot(req).await.unwrap();
662 assert_eq!(res.status(), StatusCode::OK);
663 }
664}