Compare commits

...

9 Commits

Author SHA1 Message Date
ngrondin c6a3b86964
Merge 9320df6339 into bcd0ffb016 2025-08-26 09:25:27 +01:00
Rob Ede bcd0ffb016
chore: add multi-crate publish script 2025-08-26 09:25:22 +01:00
Rob Ede 9fb6c13a1a
ci: fix msrv job 2025-08-26 08:26:49 +01:00
Rob Ede 05cfef7f4b
ci: fix msrv job 2025-08-26 08:18:34 +01:00
Rob Ede 8f3eb32a32
chore: fix justfile for msrv 2025-08-26 08:00:19 +01:00
Rob Ede ddd16ec9db
chore(actix-http): prepare release 3.11.1 2025-08-26 07:28:27 +01:00
dependabot[bot] 9c47a247fb
build(deps): bump bitflags from 2.9.2 to 2.9.3 (#3745)
Bumps [bitflags](https://github.com/bitflags/bitflags) from 2.9.2 to 2.9.3.
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.9.2...2.9.3)

---
updated-dependencies:
- dependency-name: bitflags
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 12:31:30 +00:00
Nicolas 9320df6339 Fix formatting 2025-06-01 18:37:55 +10:00
Nicolas ee7e37c62f Added max_buffer_size as a configuration item on the httpservice 2025-06-01 15:48:21 +10:00
16 changed files with 366 additions and 240 deletions

View File

@ -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 },
]

450
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,11 @@
## Unreleased
- Update `TestRequest::set_payload` to generate "Content-Length" header
- Malformed websocket frames are now gracefully rejected.
## 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

View File

@ -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"]

View File

@ -5,11 +5,11 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.11.0)](https://docs.rs/actix-http/3.11.0)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.11.1)](https://docs.rs/actix-http/3.11.1)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-http/3.11.0/status.svg)](https://deps.rs/crate/actix-http/3.11.0)
[![dependency status](https://deps.rs/crate/actix-http/3.11.1/status.svg)](https://deps.rs/crate/actix-http/3.11.1)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@ -17,6 +17,7 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
keep_alive: KeepAlive,
client_request_timeout: Duration,
client_disconnect_timeout: Duration,
max_buffer_size: Option<usize>,
secure: bool,
local_addr: Option<net::SocketAddr>,
expect: X,
@ -38,6 +39,7 @@ where
keep_alive: KeepAlive::default(),
client_request_timeout: Duration::from_secs(5),
client_disconnect_timeout: Duration::ZERO,
max_buffer_size: None,
secure: false,
local_addr: None,
@ -124,6 +126,15 @@ where
self.client_disconnect_timeout(dur)
}
/// Set maximum buffer size.
///
/// Defines the maximum size of the buffer. When the size is reached, the dispatcher
/// will flush the data to the IO streams
pub fn max_buffer_size(mut self, size: usize) -> Self {
self.max_buffer_size = Some(size);
self
}
/// Provide service for `EXPECT: 100-Continue` support.
///
/// Service get called with request that contains `EXPECT` header.
@ -140,6 +151,7 @@ where
keep_alive: self.keep_alive,
client_request_timeout: self.client_request_timeout,
client_disconnect_timeout: self.client_disconnect_timeout,
max_buffer_size: self.max_buffer_size,
secure: self.secure,
local_addr: self.local_addr,
expect: expect.into_factory(),
@ -164,6 +176,7 @@ where
keep_alive: self.keep_alive,
client_request_timeout: self.client_request_timeout,
client_disconnect_timeout: self.client_disconnect_timeout,
max_buffer_size: self.max_buffer_size,
secure: self.secure,
local_addr: self.local_addr,
expect: self.expect,
@ -199,6 +212,7 @@ where
self.keep_alive,
self.client_request_timeout,
self.client_disconnect_timeout,
self.max_buffer_size,
self.secure,
self.local_addr,
);
@ -224,6 +238,7 @@ where
self.keep_alive,
self.client_request_timeout,
self.client_disconnect_timeout,
self.max_buffer_size,
self.secure,
self.local_addr,
);
@ -246,6 +261,7 @@ where
self.keep_alive,
self.client_request_timeout,
self.client_disconnect_timeout,
self.max_buffer_size,
self.secure,
self.local_addr,
);

View File

@ -17,6 +17,7 @@ struct Inner {
keep_alive: KeepAlive,
client_request_timeout: Duration,
client_disconnect_timeout: Duration,
max_buffer_size: Option<usize>,
secure: bool,
local_addr: Option<std::net::SocketAddr>,
date_service: DateService,
@ -28,6 +29,7 @@ impl Default for ServiceConfig {
KeepAlive::default(),
Duration::from_secs(5),
Duration::ZERO,
None,
false,
None,
)
@ -40,6 +42,7 @@ impl ServiceConfig {
keep_alive: KeepAlive,
client_request_timeout: Duration,
client_disconnect_timeout: Duration,
max_buffer_size: Option<usize>,
secure: bool,
local_addr: Option<net::SocketAddr>,
) -> ServiceConfig {
@ -47,6 +50,7 @@ impl ServiceConfig {
keep_alive: keep_alive.normalize(),
client_request_timeout,
client_disconnect_timeout,
max_buffer_size,
secure,
local_addr,
date_service: DateService::new(),
@ -104,6 +108,10 @@ impl ServiceConfig {
self.0.date_service.now()
}
pub fn max_buffer_size(&self) -> Option<usize> {
self.0.max_buffer_size
}
/// Writes date header to `dst` buffer.
///
/// Low-level method that utilizes the built-in efficient date service, requiring fewer syscalls
@ -143,8 +151,14 @@ mod tests {
#[actix_rt::test]
async fn test_date_service_update() {
let settings =
ServiceConfig::new(KeepAlive::Os, Duration::ZERO, Duration::ZERO, false, None);
let settings = ServiceConfig::new(
KeepAlive::Os,
Duration::ZERO,
Duration::ZERO,
None,
false,
None,
);
yield_now().await;

View File

@ -166,6 +166,7 @@ pin_project! {
pub(super) io: Option<T>,
read_buf: BytesMut,
write_buf: BytesMut,
max_buffer_size: usize,
codec: Codec,
}
}
@ -278,6 +279,7 @@ where
io: Some(io),
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
max_buffer_size: config.max_buffer_size().unwrap_or(MAX_BUFFER_SIZE),
codec: Codec::new(config),
},
},
@ -493,7 +495,7 @@ where
StateProj::SendPayload { mut body } => {
// keep populate writer buffer until buffer size limit hit,
// get blocked or finished.
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
while this.write_buf.len() < *this.max_buffer_size {
match body.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => {
this.codec
@ -532,7 +534,7 @@ where
// keep populate writer buffer until buffer size limit hit,
// get blocked or finished.
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
while this.write_buf.len() < *this.max_buffer_size {
match body.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => {
this.codec

View File

@ -82,6 +82,7 @@ async fn late_request() {
KeepAlive::Disabled,
Duration::from_millis(100),
Duration::ZERO,
None,
false,
None,
);
@ -149,6 +150,7 @@ async fn oneshot_connection() {
KeepAlive::Disabled,
Duration::from_millis(100),
Duration::ZERO,
None,
false,
None,
);
@ -210,6 +212,7 @@ async fn keep_alive_timeout() {
KeepAlive::Timeout(Duration::from_millis(200)),
Duration::from_millis(100),
Duration::ZERO,
None,
false,
None,
);
@ -289,6 +292,7 @@ async fn keep_alive_follow_up_req() {
KeepAlive::Timeout(Duration::from_millis(500)),
Duration::from_millis(100),
Duration::ZERO,
None,
false,
None,
);
@ -453,6 +457,7 @@ async fn pipelining_ok_then_ok() {
KeepAlive::Disabled,
Duration::from_millis(1),
Duration::from_millis(1),
None,
false,
None,
);
@ -523,6 +528,7 @@ async fn pipelining_ok_then_bad() {
KeepAlive::Disabled,
Duration::from_millis(1),
Duration::from_millis(1),
None,
false,
None,
);
@ -586,6 +592,7 @@ async fn expect_handling() {
KeepAlive::Disabled,
Duration::ZERO,
Duration::ZERO,
None,
false,
None,
);
@ -663,6 +670,7 @@ async fn expect_eager() {
KeepAlive::Disabled,
Duration::ZERO,
Duration::ZERO,
None,
false,
None,
);
@ -746,6 +754,7 @@ async fn upgrade_handling() {
KeepAlive::Disabled,
Duration::ZERO,
Duration::ZERO,
None,
false,
None,
);

View File

@ -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),

View File

@ -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.

View File

@ -31,6 +31,7 @@ struct Config {
keep_alive: KeepAlive,
client_request_timeout: Duration,
client_disconnect_timeout: Duration,
max_buffer_size: Option<usize>,
#[allow(dead_code)] // only dead when no TLS features are enabled
tls_handshake_timeout: Option<Duration>,
}
@ -116,6 +117,7 @@ where
keep_alive: KeepAlive::default(),
client_request_timeout: Duration::from_secs(5),
client_disconnect_timeout: Duration::from_secs(1),
max_buffer_size: None,
tls_handshake_timeout: None,
})),
backlog: 1024,
@ -234,6 +236,15 @@ where
self
}
/// Set maximum buffer size.
///
/// Defines the maximum size of the write buffer. When the size is reached, the dispatcher
/// will flush the data to the IO streams
pub fn max_buffer_size(self, size: usize) -> Self {
self.config.lock().unwrap().max_buffer_size = Some(size);
self
}
/// Sets TLS handshake timeout.
///
/// Defines a timeout for TLS handshake. If the TLS handshake does not complete within this
@ -560,6 +571,10 @@ where
.client_disconnect_timeout(cfg.client_disconnect_timeout)
.local_addr(addr);
if let Some(size) = cfg.max_buffer_size {
svc = svc.max_buffer_size(size);
};
if let Some(handler) = on_connect_fn.clone() {
svc =
svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext))

View File

@ -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.

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# developed on macOS and probably doesn't work on Linux yet due to minor
# differences in flags on sed

View File

@ -1,38 +0,0 @@
#!/bin/sh
# run tests matching what CI does for non-linux feature sets
set -x
EXIT=0
save_exit_code() {
eval $@
local CMD_EXIT=$?
[ "$CMD_EXIT" = "0" ] || EXIT=$CMD_EXIT
}
save_exit_code cargo test --lib --tests -p=actix-router --all-features -- --nocapture
save_exit_code cargo test --lib --tests -p=actix-http --all-features -- --nocapture
save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --nocapture
save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features -- --nocapture
save_exit_code cargo test --lib --tests -p=awc --all-features -- --nocapture
save_exit_code cargo test --lib --tests -p=actix-http-test --all-features -- --nocapture
save_exit_code cargo test --lib --tests -p=actix-test --all-features -- --nocapture
save_exit_code cargo test --lib --tests -p=actix-files -- --nocapture
save_exit_code cargo test --lib --tests -p=actix-multipart --all-features -- --nocapture
save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features -- --nocapture
save_exit_code cargo test --workspace --doc
if [ "$EXIT" = "0" ]; then
PASSED="All tests passed!"
if [ "$(command -v figlet)" ]; then
figlet "$PASSED"
else
echo "$PASSED"
fi
fi
exit $EXIT

25
scripts/publish Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -Euo pipefail
for dir in $@; do
cd "$dir"
cargo publish --dry-run
read -p "Look okay? "
read -p "Sure? "
cargo publish
if [ $? -ne 0 ]; then
echo
read -p "Was the above error caused by cyclic dev-deps? Choosing yes will publish without a git backreference. (y/N) " publish_no_dev_deps
if [[ "$publish_no_dev_deps" == "y" || "$publish_no_dev_deps" == "Y" ]]; then
cargo hack --no-dev-deps publish --allow-dirty
fi
fi
cd ..
done