1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-05-20 09:18:26 +00:00

add payload to_bytes helpers (#3083)

This commit is contained in:
Rob Ede 2023-07-22 02:02:29 +01:00 committed by GitHub
parent 3eb5a059ad
commit 146011018e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 117 additions and 7 deletions

View file

@ -47,9 +47,8 @@ where
/// Attempts to pull out the next value of the underlying [`Stream`].
///
/// Empty values are skipped to prevent [`BodyStream`]'s transmission being
/// ended on a zero-length chunk, but rather proceed until the underlying
/// [`Stream`] ends.
/// Empty values are skipped to prevent [`BodyStream`]'s transmission being ended on a
/// zero-length chunk, but rather proceed until the underlying [`Stream`] ends.
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,

View file

@ -6,7 +6,8 @@
- Add `HttpServer::{bind, listen}_auto_h2c()` method behind new `http2` crate feature.
- Add `Resource::{get, post, etc...}` methods for more concisely adding routes that don't need additional guards.
- Add several missing convenience methods on `HttpResponse` for their respective status codes.
- Add `web::Payload::to_bytes[_limited]()` helper methods.
- Add missing constructors on `HttpResponse` for several status codes.
### Changed

View file

@ -16,7 +16,8 @@ use futures_core::{ready, stream::Stream};
use mime::Mime;
use crate::{
dev, error::ErrorBadRequest, http::header, web, Error, FromRequest, HttpMessage, HttpRequest,
body, dev, error::ErrorBadRequest, http::header, web, Error, FromRequest, HttpMessage,
HttpRequest,
};
/// Extract a request's raw payload stream.
@ -50,6 +51,72 @@ impl Payload {
pub fn into_inner(self) -> dev::Payload {
self.0
}
/// Buffers payload from request up to `limit` bytes.
///
/// This method is preferred over [`Payload::to_bytes()`] since it will not lead to unexpected
/// memory exhaustion from massive payloads. Note that the other primitive extractors such as
/// [`Bytes`] and [`String`], as well as extractors built on top of them, already have this sort
/// of protection according to the configured (or default) [`PayloadConfig`].
///
/// # Errors
///
/// - The outer error type, [`BodyLimitExceeded`](body::BodyLimitExceeded), is returned when the
/// payload is larger than `limit`.
/// - The inner error type is [the normal Actix Web error](crate::Error) and is only returned if
/// the payload stream yields an error for some reason. Such cases are usually caused by
/// unrecoverable connection issues.
///
/// # Examples
///
/// ```
/// use actix_web::{error, web::Payload, Responder};
///
/// async fn limited_payload_handler(pl: Payload) -> actix_web::Result<impl Responder> {
/// match pl.to_bytes_limited(5).await {
/// Ok(res) => res,
/// Err(err) => Err(error::ErrorPayloadTooLarge(err)),
/// }
/// }
/// ```
pub async fn to_bytes_limited(
self,
limit: usize,
) -> Result<crate::Result<Bytes>, body::BodyLimitExceeded> {
let stream = body::BodyStream::new(self.0);
match body::to_bytes_limited(stream, limit).await {
Ok(Ok(body)) => Ok(Ok(body)),
Ok(Err(err)) => Ok(Err(err.into())),
Err(err) => Err(err),
}
}
/// Buffers entire payload from request.
///
/// Use of this method is discouraged unless you know for certain that requests will not be
/// large enough to exhaust memory. If this is not known, prefer [`Payload::to_bytes_limited()`]
/// or one of the higher level extractors like [`Bytes`] or [`String`] that implement size
/// limits according to the configured (or default) [`PayloadConfig`].
///
/// # Errors
///
/// An error is only returned if the payload stream yields an error for some reason. Such cases
/// are usually caused by unrecoverable connection issues.
///
/// # Examples
///
/// ```
/// use actix_web::{error, web::Payload, Responder};
///
/// async fn payload_handler(pl: Payload) -> actix_web::Result<impl Responder> {
/// pl.to_bytes().await
/// }
/// ```
pub async fn to_bytes(self) -> crate::Result<Bytes> {
let stream = body::BodyStream::new(self.0);
Ok(body::to_bytes(stream).await?)
}
}
impl Stream for Payload {
@ -64,7 +131,7 @@ impl Stream for Payload {
/// See [here](#Examples) for example of usage as an extractor.
impl FromRequest for Payload {
type Error = Error;
type Future = Ready<Result<Payload, Error>>;
type Future = Ready<Result<Self, Self::Error>>;
#[inline]
fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
@ -378,10 +445,53 @@ mod tests {
use super::*;
use crate::{
http::{header, StatusCode},
test::{call_service, init_service, TestRequest},
test::{call_service, init_service, read_body, TestRequest},
web, App, Responder,
};
#[actix_rt::test]
async fn payload_to_bytes() {
async fn payload_handler(pl: Payload) -> crate::Result<impl Responder> {
pl.to_bytes().await
}
async fn limited_payload_handler(pl: Payload) -> crate::Result<impl Responder> {
match pl.to_bytes_limited(5).await {
Ok(res) => res,
Err(_limited) => Err(ErrorBadRequest("too big")),
}
}
let srv = init_service(
App::new()
.route("/all", web::to(payload_handler))
.route("limited", web::to(limited_payload_handler)),
)
.await;
let req = TestRequest::with_uri("/all")
.set_payload("1234567890")
.to_request();
let res = call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
let body = read_body(res).await;
assert_eq!(body, "1234567890");
let req = TestRequest::with_uri("/limited")
.set_payload("1234567890")
.to_request();
let res = call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_uri("/limited")
.set_payload("12345")
.to_request();
let res = call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
let body = read_body(res).await;
assert_eq!(body, "12345");
}
#[actix_rt::test]
async fn test_payload_config() {
let req = TestRequest::default().to_http_request();