mirror of https://github.com/fafhrd91/actix-web
Compare commits
20 Commits
f8e6cf78f4
...
a1d6e8e1f2
Author | SHA1 | Date |
---|---|---|
|
a1d6e8e1f2 | |
|
9fb6c13a1a | |
|
05cfef7f4b | |
|
8f3eb32a32 | |
|
ddd16ec9db | |
|
9c47a247fb | |
|
2536823e3b | |
|
e3f81d0643 | |
|
a84aee836b | |
|
1f08cb24c3 | |
|
1b49047086 | |
|
9fe033a963 | |
|
2ba69a1904 | |
|
c6352005f7 | |
|
aeb42486af | |
|
30f7df5853 | |
|
e3ae20de30 | |
|
5f6c84494a | |
|
6eac21683d | |
|
a3e70dcfbc |
|
@ -3,6 +3,6 @@ disallowed-names = [
|
|||
"e", # no single letter error bindings
|
||||
]
|
||||
disallowed-methods = [
|
||||
{ path = "std::cell::RefCell::default()", reason = "prefer explicit inner type default" },
|
||||
{ path = "std::rc::Rc::default()", reason = "prefer explicit inner type default" },
|
||||
{ path = "std::cell::RefCell::default()", reason = "prefer explicit inner type default (remove allow-invalid when rust-lang/rust-clippy/#8581 is fixed)", allow-invalid = true },
|
||||
{ path = "std::rc::Rc::default()", reason = "prefer explicit inner type default (remove allow-invalid when rust-lang/rust-clippy/#8581 is fixed)", allow-invalid = true },
|
||||
]
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: [robjtede]
|
||||
github: [robjtede, JohnTitor]
|
||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Install Rust
|
||||
run: |
|
||||
|
|
|
@ -28,11 +28,11 @@ jobs:
|
|||
runs-on: ${{ matrix.target.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Install nasm
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
uses: ilammy/setup-nasm@v1.5.2
|
||||
uses: ilammy/setup-nasm@72793074d3c8cdda771dba85f6deafe00623038b # v1.5.2
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
|
@ -44,12 +44,12 @@ jobs:
|
|||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust (${{ matrix.version.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.13.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
|
||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.58.17
|
||||
uses: taiki-e/install-action@f63c33fd96cc1e69a29bafd06541cf28588b43a4 # v2.58.21
|
||||
with:
|
||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||
|
||||
|
@ -71,19 +71,19 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Free Disk Space
|
||||
run: ./scripts/free-disk-space.sh
|
||||
|
||||
- name: Setup mold linker
|
||||
uses: rui314/setup-mold@v1
|
||||
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.13.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
|
||||
|
||||
- name: Install just, cargo-hack
|
||||
uses: taiki-e/install-action@v2.58.17
|
||||
uses: taiki-e/install-action@f63c33fd96cc1e69a29bafd06541cf28588b43a4 # v2.58.21
|
||||
with:
|
||||
tool: just,cargo-hack
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ concurrency:
|
|||
jobs:
|
||||
read_msrv:
|
||||
name: Read MSRV
|
||||
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@v0.1.0
|
||||
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@8b553824444060021f2843d7b4d803f3624d15e5 # v0.1.0
|
||||
|
||||
build_and_test:
|
||||
needs: read_msrv
|
||||
|
@ -39,11 +39,11 @@ jobs:
|
|||
runs-on: ${{ matrix.target.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Install nasm
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
uses: ilammy/setup-nasm@v1.5.2
|
||||
uses: ilammy/setup-nasm@72793074d3c8cdda771dba85f6deafe00623038b # v1.5.2
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
|
@ -56,15 +56,15 @@ jobs:
|
|||
|
||||
- name: Setup mold linker
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
uses: rui314/setup-mold@v1
|
||||
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
|
||||
|
||||
- name: Install Rust (${{ matrix.version.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.13.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
|
||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.58.17
|
||||
uses: taiki-e/install-action@f63c33fd96cc1e69a29bafd06541cf28588b43a4 # v2.58.21
|
||||
with:
|
||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||
|
||||
|
@ -89,10 +89,10 @@ jobs:
|
|||
name: io-uring tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.13.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
|
@ -105,15 +105,15 @@ jobs:
|
|||
name: doc tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.13.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2.58.17
|
||||
uses: taiki-e/install-action@f63c33fd96cc1e69a29bafd06541cf28588b43a4 # v2.58.21
|
||||
with:
|
||||
tool: just
|
||||
|
||||
|
|
|
@ -15,16 +15,16 @@ jobs:
|
|||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.13.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: llvm-tools
|
||||
|
||||
- name: Install just, cargo-llvm-cov, cargo-nextest
|
||||
uses: taiki-e/install-action@v2.58.17
|
||||
uses: taiki-e/install-action@f63c33fd96cc1e69a29bafd06541cf28588b43a4 # v2.58.21
|
||||
with:
|
||||
tool: just,cargo-llvm-cov,cargo-nextest
|
||||
|
||||
|
@ -32,7 +32,7 @@ jobs:
|
|||
run: just test-coverage-codecov
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5.4.3
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
with:
|
||||
files: codecov.json
|
||||
fail_ci_if_error: true
|
||||
|
|
|
@ -15,10 +15,10 @@ jobs:
|
|||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.13.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
|
@ -33,15 +33,15 @@ jobs:
|
|||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.13.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Check with Clippy
|
||||
uses: giraffate/clippy-action@v1.0.1
|
||||
uses: giraffate/clippy-action@13b9d32482f25d29ead141b79e7e04e7900281e0 # v1.0.1
|
||||
with:
|
||||
reporter: github-pr-check
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
@ -52,10 +52,10 @@ jobs:
|
|||
lint-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.13.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rust-docs
|
||||
|
@ -69,20 +69,20 @@ jobs:
|
|||
if: false # rustdoc mismatch currently
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Install Rust (${{ vars.RUST_VERSION_EXTERNAL_TYPES }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.13.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@ab6845274e2ff01cd4462007e1a9d9df1ab49f42 # v1.14.0
|
||||
with:
|
||||
toolchain: ${{ vars.RUST_VERSION_EXTERNAL_TYPES }}
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2.58.17
|
||||
uses: taiki-e/install-action@f63c33fd96cc1e69a29bafd06541cf28588b43a4 # v2.58.21
|
||||
with:
|
||||
tool: just
|
||||
|
||||
- name: Install cargo-check-external-types
|
||||
uses: taiki-e/cache-cargo-install-action@v2.3.0
|
||||
uses: taiki-e/cache-cargo-install-action@b33c63d3b3c85540f4eba8a4f71a5cc0ce030855 # v2.3.0
|
||||
with:
|
||||
tool: cargo-check-external-types
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,6 +2,12 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
## 3.11.1
|
||||
|
||||
- Prevent more hangs after client disconnects.
|
||||
- More malformed WebSocket frames are now gracefully rejected.
|
||||
- Using `TestRequest::set_payload()` now sets a Content-Length header.
|
||||
|
||||
## 3.11.0
|
||||
|
||||
- Update `brotli` dependency to `8`.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-http"
|
||||
version = "3.11.0"
|
||||
version = "3.11.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rob Ede <robjtede@icloud.com>"]
|
||||
description = "HTTP types and services for the Actix ecosystem"
|
||||
keywords = ["actix", "http", "framework", "async", "futures"]
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://docs.rs/actix-http/3.11.0)
|
||||
[](https://docs.rs/actix-http/3.11.1)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-http/3.11.0)
|
||||
[](https://deps.rs/crate/actix-http/3.11.1)
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
|
|
@ -1182,7 +1182,7 @@ where
|
|||
let state_is_none = inner_p.state.is_none();
|
||||
|
||||
// read half is closed; we do not process any responses
|
||||
if inner_p.flags.contains(Flags::READ_DISCONNECT) && state_is_none {
|
||||
if inner_p.flags.contains(Flags::READ_DISCONNECT) {
|
||||
trace!("read half closed; start shutdown");
|
||||
inner_p.flags.insert(Flags::SHUTDOWN);
|
||||
}
|
||||
|
@ -1216,6 +1216,9 @@ where
|
|||
inner_p.shutdown_timer,
|
||||
);
|
||||
|
||||
if inner_p.flags.contains(Flags::SHUTDOWN) {
|
||||
cx.waker().wake_by_ref();
|
||||
}
|
||||
Poll::Pending
|
||||
};
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
|||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use http::{Method, Uri, Version};
|
||||
use http::{header, Method, Uri, Version};
|
||||
|
||||
use crate::{
|
||||
header::{HeaderMap, TryIntoHeaderPair},
|
||||
|
@ -98,9 +98,13 @@ impl TestRequest {
|
|||
}
|
||||
|
||||
/// Set request payload.
|
||||
///
|
||||
/// This sets the `Content-Length` header with the size of `data`.
|
||||
pub fn set_payload(&mut self, data: impl Into<Bytes>) -> &mut Self {
|
||||
let mut payload = crate::h1::Payload::empty();
|
||||
payload.unread_data(data.into());
|
||||
let bytes = data.into();
|
||||
self.insert_header((header::CONTENT_LENGTH, bytes.len()));
|
||||
payload.unread_data(bytes);
|
||||
parts(&mut self.0).payload = Some(payload.into());
|
||||
self
|
||||
}
|
||||
|
|
|
@ -94,11 +94,21 @@ impl Parser {
|
|||
Some(res) => res,
|
||||
};
|
||||
|
||||
let frame_len = match idx.checked_add(length) {
|
||||
Some(len) => len,
|
||||
None => return Err(ProtocolError::Overflow),
|
||||
};
|
||||
|
||||
// not enough data
|
||||
if src.len() < idx + length {
|
||||
if src.len() < frame_len {
|
||||
let min_length = min(length, max_size);
|
||||
if src.capacity() < idx + min_length {
|
||||
src.reserve(idx + min_length - src.capacity());
|
||||
let required_cap = match idx.checked_add(min_length) {
|
||||
Some(cap) => cap,
|
||||
None => return Err(ProtocolError::Overflow),
|
||||
};
|
||||
|
||||
if src.capacity() < required_cap {
|
||||
src.reserve(required_cap - src.capacity());
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
|
@ -402,4 +412,14 @@ mod tests {
|
|||
Parser::write_close(&mut buf, None, false);
|
||||
assert_eq!(&buf[..], &vec![0x88, 0x00][..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_length_overflow() {
|
||||
let buf: [u8; 14] = [
|
||||
0x0a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xeb, 0x0e, 0x8f,
|
||||
];
|
||||
let mut buf = BytesMut::from(&buf[..]);
|
||||
let result = Parser::parse(&mut buf, true, 65536);
|
||||
assert!(matches!(result, Err(ProtocolError::Overflow)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -187,6 +187,45 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'t, T> FieldGroupReader<'t> for Option<Vec<T>>
|
||||
where
|
||||
T: FieldReader<'t>,
|
||||
{
|
||||
type Future = LocalBoxFuture<'t, Result<(), MultipartError>>;
|
||||
|
||||
fn handle_field(
|
||||
req: &'t HttpRequest,
|
||||
field: Field,
|
||||
limits: &'t mut Limits,
|
||||
state: &'t mut State,
|
||||
_duplicate_field: DuplicateField,
|
||||
) -> Self::Future {
|
||||
let field_name = field.name().unwrap().to_string();
|
||||
|
||||
Box::pin(async move {
|
||||
let vec = state
|
||||
.entry(field_name)
|
||||
.or_insert_with(|| Box::<Vec<T>>::default())
|
||||
.downcast_mut::<Vec<T>>()
|
||||
.unwrap();
|
||||
|
||||
let item = T::read_field(req, field, limits).await?;
|
||||
vec.push(item);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn from_state(name: &str, state: &'t mut State) -> Result<Self, MultipartError> {
|
||||
if let Some(boxed_vec) = state.remove(name) {
|
||||
let vec = *boxed_vec.downcast::<Vec<T>>().unwrap();
|
||||
Ok(Some(vec))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait that allows a type to be used in the [`struct@MultipartForm`] extractor.
|
||||
///
|
||||
/// You should use the [`macro@MultipartForm`] macro to derive this for your struct.
|
||||
|
|
|
@ -13,6 +13,7 @@ macro_rules! register {
|
|||
register!(finish => "(.*)", "(.*)", "(.*)", "(.*)")
|
||||
}};
|
||||
(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
|
||||
#[expect(clippy::useless_concat)]
|
||||
let arr = [
|
||||
concat!("/authorizations"),
|
||||
concat!("/authorizations/", $p1),
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
- `actix_web::response::builder::HttpResponseBuilder::streaming()` now sets `Content-Type` to `application/octet-stream` if `Content-Type` does not exist.
|
||||
- `actix_web::response::builder::HttpResponseBuilder::streaming()` now calls `actix_web::response::builder::HttpResponseBuilder::no_chunking()` if `Content-Length` is set by user.
|
||||
|
||||
## 4.11.0
|
||||
|
||||
- Add `Logger::log_level()` method.
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
- The return type for `ServiceRequest::app_data::<T>()` was changed from returning a `Data<T>` to simply a `T`. To access a `Data<T>` use `ServiceRequest::app_data::<Data<T>>()`.
|
||||
|
||||
- Cookie handling has been offloaded to the `cookie` crate:
|
||||
|
||||
- `USERINFO_ENCODE_SET` is no longer exposed. Percent-encoding is still supported; check docs.
|
||||
- Some types now require lifetime parameters.
|
||||
|
||||
|
|
|
@ -318,12 +318,33 @@ impl HttpResponseBuilder {
|
|||
/// Set a streaming body and build the `HttpResponse`.
|
||||
///
|
||||
/// `HttpResponseBuilder` can not be used after this call.
|
||||
///
|
||||
/// If `Content-Type` is not set, then it is automatically set to `application/octet-stream`.
|
||||
///
|
||||
/// If `Content-Length` is set, then [`no_chunking()`](Self::no_chunking) is automatically called.
|
||||
#[inline]
|
||||
pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + 'static,
|
||||
E: Into<BoxError> + 'static,
|
||||
{
|
||||
// Set mime type to application/octet-stream if it is not set
|
||||
if let Some(parts) = self.inner() {
|
||||
if !parts.headers.contains_key(header::CONTENT_TYPE) {
|
||||
self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_OCTET_STREAM));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(parts) = self.inner() {
|
||||
if let Some(length) = parts.headers.get(header::CONTENT_LENGTH) {
|
||||
if let Ok(length) = length.to_str() {
|
||||
if let Ok(length) = length.parse::<u64>() {
|
||||
self.no_chunking(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.body(BodyStream::new(stream))
|
||||
}
|
||||
|
||||
|
|
|
@ -616,7 +616,7 @@ mod tests {
|
|||
}
|
||||
));
|
||||
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
let (mut req, mut pl) = TestRequest::default()
|
||||
.insert_header((
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
|
@ -624,6 +624,7 @@ mod tests {
|
|||
.set_payload(Bytes::from_static(&[0u8; 1000]))
|
||||
.to_http_parts();
|
||||
|
||||
req.head_mut().headers_mut().remove(header::CONTENT_LENGTH);
|
||||
let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true)
|
||||
.limit(100)
|
||||
.await;
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
use std::{
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_web::{
|
||||
http::header::{self, HeaderValue},
|
||||
HttpResponse,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures_core::Stream;
|
||||
|
||||
struct FixedSizeStream {
|
||||
data: Vec<u8>,
|
||||
yielded: bool,
|
||||
}
|
||||
|
||||
impl FixedSizeStream {
|
||||
fn new(size: usize) -> Self {
|
||||
Self {
|
||||
data: vec![0u8; size],
|
||||
yielded: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for FixedSizeStream {
|
||||
type Item = Result<Bytes, std::io::Error>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
if self.yielded {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
self.yielded = true;
|
||||
let data = std::mem::take(&mut self.data);
|
||||
Poll::Ready(Some(Ok(Bytes::from(data))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_streaming_response_with_content_length() {
|
||||
let stream = FixedSizeStream::new(100);
|
||||
|
||||
let resp = HttpResponse::Ok()
|
||||
.append_header((header::CONTENT_LENGTH, "100"))
|
||||
.streaming(stream);
|
||||
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_LENGTH),
|
||||
Some(&HeaderValue::from_static("100")),
|
||||
"Content-Length should be preserved when explicitly set"
|
||||
);
|
||||
|
||||
let has_chunked = resp
|
||||
.headers()
|
||||
.get(header::TRANSFER_ENCODING)
|
||||
.map(|v| v.to_str().unwrap_or(""))
|
||||
.unwrap_or("")
|
||||
.contains("chunked");
|
||||
|
||||
assert!(
|
||||
!has_chunked,
|
||||
"chunked should not be used when Content-Length is provided"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE),
|
||||
Some(&HeaderValue::from_static("application/octet-stream")),
|
||||
"Content-Type should default to application/octet-stream"
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_streaming_response_default_content_type() {
|
||||
let stream = FixedSizeStream::new(50);
|
||||
|
||||
let resp = HttpResponse::Ok().streaming(stream);
|
||||
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE),
|
||||
Some(&HeaderValue::from_static("application/octet-stream")),
|
||||
"Content-Type should default to application/octet-stream"
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_streaming_response_user_defined_content_type() {
|
||||
let stream = FixedSizeStream::new(25);
|
||||
|
||||
let resp = HttpResponse::Ok()
|
||||
.insert_header((header::CONTENT_TYPE, "text/plain"))
|
||||
.streaming(stream);
|
||||
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE),
|
||||
Some(&HeaderValue::from_static("text/plain")),
|
||||
"User-defined Content-Type should be preserved"
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_streaming_response_empty_stream() {
|
||||
let stream = FixedSizeStream::new(0);
|
||||
|
||||
let resp = HttpResponse::Ok()
|
||||
.append_header((header::CONTENT_LENGTH, "0"))
|
||||
.streaming(stream);
|
||||
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_LENGTH),
|
||||
Some(&HeaderValue::from_static("0")),
|
||||
"Content-Length 0 should be preserved for empty streams"
|
||||
);
|
||||
}
|
8
justfile
8
justfile
|
@ -13,6 +13,8 @@ fmt:
|
|||
[private]
|
||||
downgrade-for-msrv:
|
||||
cargo {{ toolchain }} update -p=divan --precise=0.1.15 # next ver: 1.80.0
|
||||
cargo {{ toolchain }} update -p=rayon --precise=1.10.0 # next ver: 1.80.0
|
||||
cargo {{ toolchain }} update -p=rayon-core --precise=1.12.1 # next ver: 1.80.0
|
||||
cargo {{ toolchain }} update -p=half --precise=2.4.1 # next ver: 1.81.0
|
||||
cargo {{ toolchain }} update -p=idna_adapter --precise=1.2.0 # next ver: 1.82.0
|
||||
cargo {{ toolchain }} update -p=litemap --precise=0.7.4 # next ver: 1.81.0
|
||||
|
@ -50,8 +52,7 @@ clippy:
|
|||
cargo {{ toolchain }} clippy --workspace --all-targets {{ all_crate_features }}
|
||||
|
||||
# Run Clippy over workspace using MSRV.
|
||||
clippy-msrv:
|
||||
@just toolchain={{ msrv_rustup }} downgrade-for-msrv
|
||||
clippy-msrv: downgrade-for-msrv
|
||||
@just toolchain={{ msrv_rustup }} clippy
|
||||
|
||||
# Test workspace code.
|
||||
|
@ -62,8 +63,7 @@ test:
|
|||
cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-web-codegen --exclude=actix-multipart-derive {{ all_crate_features }} --filter-expr="not test(test_reading_deflate_encoding_large_random_rustls)"
|
||||
|
||||
# Test workspace using MSRV.
|
||||
test-msrv:
|
||||
@just toolchain={{ msrv_rustup }} downgrade-for-msrv
|
||||
test-msrv: downgrade-for-msrv
|
||||
@just toolchain={{ msrv_rustup }} test
|
||||
|
||||
# Test workspace docs.
|
||||
|
|
Loading…
Reference in New Issue