mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into scope_work
This commit is contained in:
commit
47e4950522
|
@ -49,7 +49,7 @@ jobs:
|
||||||
toolchain: ${{ matrix.version.version }}
|
toolchain: ${{ matrix.version.version }}
|
||||||
|
|
||||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||||
uses: taiki-e/install-action@v2.33.34
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ jobs:
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
|
|
||||||
- name: Install cargo-hack
|
- name: Install cargo-hack
|
||||||
uses: taiki-e/install-action@v2.33.34
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: cargo-hack
|
tool: cargo-hack
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ concurrency:
|
||||||
jobs:
|
jobs:
|
||||||
read_msrv:
|
read_msrv:
|
||||||
name: Read MSRV
|
name: Read MSRV
|
||||||
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@main
|
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@v0.1.0
|
||||||
|
|
||||||
build_and_test:
|
build_and_test:
|
||||||
needs: read_msrv
|
needs: read_msrv
|
||||||
|
@ -64,7 +64,7 @@ jobs:
|
||||||
toolchain: ${{ matrix.version.version }}
|
toolchain: ${{ matrix.version.version }}
|
||||||
|
|
||||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||||
uses: taiki-e/install-action@v2.33.34
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ jobs:
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
|
|
||||||
- name: Install just
|
- name: Install just
|
||||||
uses: taiki-e/install-action@v2.33.34
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: just
|
tool: just
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ jobs:
|
||||||
components: llvm-tools-preview
|
components: llvm-tools-preview
|
||||||
|
|
||||||
- name: Install just,cargo-llvm-cov
|
- name: Install just,cargo-llvm-cov
|
||||||
uses: taiki-e/install-action@v2.33.34
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: just,cargo-llvm-cov
|
tool: just,cargo-llvm-cov
|
||||||
|
|
||||||
|
|
|
@ -79,10 +79,10 @@ jobs:
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||||
with:
|
with:
|
||||||
toolchain: nightly-2024-04-26
|
toolchain: nightly-2024-06-07
|
||||||
|
|
||||||
- name: Install cargo-public-api
|
- name: Install cargo-public-api
|
||||||
uses: taiki-e/install-action@v2.33.34
|
uses: taiki-e/install-action@v2.34.0
|
||||||
with:
|
with:
|
||||||
tool: cargo-public-api
|
tool: cargo-public-api
|
||||||
|
|
||||||
|
|
|
@ -178,14 +178,14 @@ impl Parser {
|
||||||
};
|
};
|
||||||
|
|
||||||
if payload_len < 126 {
|
if payload_len < 126 {
|
||||||
dst.reserve(p_len + 2 + if mask { 4 } else { 0 });
|
dst.reserve(p_len + 2);
|
||||||
dst.put_slice(&[one, two | payload_len as u8]);
|
dst.put_slice(&[one, two | payload_len as u8]);
|
||||||
} else if payload_len <= 65_535 {
|
} else if payload_len <= 65_535 {
|
||||||
dst.reserve(p_len + 4 + if mask { 4 } else { 0 });
|
dst.reserve(p_len + 4);
|
||||||
dst.put_slice(&[one, two | 126]);
|
dst.put_slice(&[one, two | 126]);
|
||||||
dst.put_u16(payload_len as u16);
|
dst.put_u16(payload_len as u16);
|
||||||
} else {
|
} else {
|
||||||
dst.reserve(p_len + 10 + if mask { 4 } else { 0 });
|
dst.reserve(p_len + 10);
|
||||||
dst.put_slice(&[one, two | 127]);
|
dst.put_slice(&[one, two | 127]);
|
||||||
dst.put_u64(payload_len as u64);
|
dst.put_u64(payload_len as u64);
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
|
|
||||||
<!-- prettier-ignore-end -->
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
Dependencies:
|
Dependencies:
|
||||||
|
@ -65,6 +64,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
```
|
```
|
||||||
|
|
||||||
Curl request :
|
Curl request :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -v --request POST \
|
curl -v --request POST \
|
||||||
--url http://localhost:8080/videos \
|
--url http://localhost:8080/videos \
|
||||||
|
@ -72,7 +72,6 @@ curl -v --request POST \
|
||||||
-F file=@./Cargo.lock
|
-F file=@./Cargo.lock
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
https://github.com/actix/examples/tree/master/forms/multipart
|
https://github.com/actix/examples/tree/master/forms/multipart
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
- Add `TestServerConfig::rustls_0_23()` method for Rustls v0.23 support behind new `rustls-0_23` crate feature.
|
- Add `TestServerConfig::rustls_0_23()` method for Rustls v0.23 support behind new `rustls-0_23` crate feature.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Add `TestServerConfig::disable_redirects()` method.
|
||||||
- Various types from `awc`, such as `ClientRequest` and `ClientResponse`, are now re-exported.
|
- Various types from `awc`, such as `ClientRequest` and `ClientResponse`, are now re-exported.
|
||||||
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
|
|
||||||
## 0.1.3
|
## 0.1.3
|
||||||
|
|
||||||
|
|
|
@ -149,6 +149,8 @@ where
|
||||||
StreamType::Rustls023(_) => true,
|
StreamType::Rustls023(_) => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let client_cfg = cfg.clone();
|
||||||
|
|
||||||
// run server in separate orphaned thread
|
// run server in separate orphaned thread
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
rt::System::new().block_on(async move {
|
rt::System::new().block_on(async move {
|
||||||
|
@ -460,7 +462,13 @@ where
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Client::builder().connector(connector).finish()
|
let mut client_builder = Client::builder().connector(connector);
|
||||||
|
|
||||||
|
if client_cfg.disable_redirects {
|
||||||
|
client_builder = client_builder.disable_redirects();
|
||||||
|
}
|
||||||
|
|
||||||
|
client_builder.finish()
|
||||||
};
|
};
|
||||||
|
|
||||||
TestServer {
|
TestServer {
|
||||||
|
@ -480,6 +488,7 @@ enum HttpVer {
|
||||||
Both,
|
Both,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum StreamType {
|
enum StreamType {
|
||||||
Tcp,
|
Tcp,
|
||||||
|
@ -507,6 +516,7 @@ pub struct TestServerConfig {
|
||||||
client_request_timeout: Duration,
|
client_request_timeout: Duration,
|
||||||
port: u16,
|
port: u16,
|
||||||
workers: usize,
|
workers: usize,
|
||||||
|
disable_redirects: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TestServerConfig {
|
impl Default for TestServerConfig {
|
||||||
|
@ -524,6 +534,7 @@ impl TestServerConfig {
|
||||||
client_request_timeout: Duration::from_secs(5),
|
client_request_timeout: Duration::from_secs(5),
|
||||||
port: 0,
|
port: 0,
|
||||||
workers: 1,
|
workers: 1,
|
||||||
|
disable_redirects: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -611,6 +622,15 @@ impl TestServerConfig {
|
||||||
self.workers = workers;
|
self.workers = workers;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Instruct the client to not follow redirects.
|
||||||
|
///
|
||||||
|
/// By default, the client will follow up to 10 consecutive redirects
|
||||||
|
/// before giving up.
|
||||||
|
pub fn disable_redirects(mut self) -> Self {
|
||||||
|
self.disable_redirects = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A basic HTTP server controller that simplifies the process of writing integration tests for
|
/// A basic HTTP server controller that simplifies the process of writing integration tests for
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Prevent inclusion of default `actix-router` features.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||||
- Add a scope macro that takes a path
|
- Add a scope macro that takes a path
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ rust-version.workspace = true
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-router = "0.5"
|
actix-router = { version = "0.5", default-features = false }
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = { version = "2", features = ["full", "extra-traits"] }
|
syn = { version = "2", features = ["full", "extra-traits"] }
|
||||||
|
|
|
@ -20,10 +20,7 @@ error: custom attribute panicked
|
||||||
13 | #[get("/{}")]
|
13 | #[get("/{}")]
|
||||||
| ^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: message: Wrong path pattern: "/{}" regex parse error:
|
= help: message: Wrong path pattern: "/{}" empty capture group names are not allowed
|
||||||
((?s-m)^/(?P<>[^/]+))$
|
|
||||||
^
|
|
||||||
error: empty capture group name
|
|
||||||
|
|
||||||
error: custom attribute panicked
|
error: custom attribute panicked
|
||||||
--> $DIR/route-malformed-path-fail.rs:23:1
|
--> $DIR/route-malformed-path-fail.rs:23:1
|
||||||
|
|
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add `CustomizeResponder::add_cookie()` method.
|
||||||
|
- Add `guard::GuardContext::app_data()` method.
|
||||||
|
- Implement `From<Box<dyn ResponseError>>` for `Error`.
|
||||||
|
|
||||||
## 4.6.0
|
## 4.6.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -35,7 +35,6 @@ features = [
|
||||||
"secure-cookies",
|
"secure-cookies",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "actix_web"
|
name = "actix_web"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
@ -130,6 +129,7 @@ awc = { version = "3", features = ["openssl"] }
|
||||||
|
|
||||||
brotli = "6"
|
brotli = "6"
|
||||||
const-str = "0.5"
|
const-str = "0.5"
|
||||||
|
core_affinity = "0.8"
|
||||||
criterion = { version = "0.5", features = ["html_reports"] }
|
criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
flate2 = "1.0.13"
|
flate2 = "1.0.13"
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
use actix_web::{middleware, web, App, HttpServer};
|
||||||
|
|
||||||
|
async fn hello() -> &'static str {
|
||||||
|
"Hello world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> io::Result<()> {
|
||||||
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
|
|
||||||
|
let core_ids = core_affinity::get_core_ids().unwrap();
|
||||||
|
let n_core_ids = core_ids.len();
|
||||||
|
let next_core_id = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
HttpServer::new(move || {
|
||||||
|
let pin = Arc::clone(&next_core_id).fetch_add(1, Ordering::AcqRel);
|
||||||
|
log::info!(
|
||||||
|
"setting CPU affinity for worker {}: pinning to core {}",
|
||||||
|
thread::current().name().unwrap(),
|
||||||
|
pin,
|
||||||
|
);
|
||||||
|
core_affinity::set_for_current(core_ids[pin]);
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.wrap(middleware::Logger::default())
|
||||||
|
.service(web::resource("/").get(hello))
|
||||||
|
})
|
||||||
|
.bind(("127.0.0.1", 8080))?
|
||||||
|
.workers(n_core_ids)
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
|
@ -112,8 +112,8 @@ where
|
||||||
/// })
|
/// })
|
||||||
/// ```
|
/// ```
|
||||||
#[doc(alias = "manage")]
|
#[doc(alias = "manage")]
|
||||||
pub fn app_data<U: 'static>(mut self, ext: U) -> Self {
|
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
|
||||||
self.extensions.insert(ext);
|
self.extensions.insert(data);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,12 @@ impl<T: ResponseError + 'static> From<T> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Box<dyn ResponseError>> for Error {
|
||||||
|
fn from(value: Box<dyn ResponseError>) -> Self {
|
||||||
|
Error { cause: value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Error> for Response<BoxBody> {
|
impl From<Error> for Response<BoxBody> {
|
||||||
fn from(err: Error) -> Response<BoxBody> {
|
fn from(err: Error) -> Response<BoxBody> {
|
||||||
err.error_response().into()
|
err.error_response().into()
|
||||||
|
|
|
@ -110,6 +110,12 @@ impl<'a> GuardContext<'a> {
|
||||||
pub fn header<H: Header>(&self) -> Option<H> {
|
pub fn header<H: Header>(&self) -> Option<H> {
|
||||||
H::parse(self.req).ok()
|
H::parse(self.req).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Counterpart to [HttpRequest::app_data](crate::HttpRequest::app_data).
|
||||||
|
#[inline]
|
||||||
|
pub fn app_data<T: 'static>(&self) -> Option<&T> {
|
||||||
|
self.req.app_data()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interface for routing guards.
|
/// Interface for routing guards.
|
||||||
|
@ -512,4 +518,18 @@ mod tests {
|
||||||
.to_srv_request();
|
.to_srv_request();
|
||||||
assert!(guard.check(&req.guard_ctx()));
|
assert!(guard.check(&req.guard_ctx()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn app_data() {
|
||||||
|
const TEST_VALUE: u32 = 42;
|
||||||
|
let guard = fn_guard(|ctx| dbg!(ctx.app_data::<u32>()) == Some(&TEST_VALUE));
|
||||||
|
|
||||||
|
let req = TestRequest::default().app_data(TEST_VALUE).to_srv_request();
|
||||||
|
assert!(guard.check(&req.guard_ctx()));
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.app_data(TEST_VALUE * 2)
|
||||||
|
.to_srv_request();
|
||||||
|
assert!(!guard.check(&req.guard_ctx()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use actix_http::{
|
||||||
|
|
||||||
use crate::{HttpRequest, HttpResponse, Responder};
|
use crate::{HttpRequest, HttpResponse, Responder};
|
||||||
|
|
||||||
/// Allows overriding status code and headers for a [`Responder`].
|
/// Allows overriding status code and headers (including cookies) for a [`Responder`].
|
||||||
///
|
///
|
||||||
/// Created by calling the [`customize`](Responder::customize) method on a [`Responder`] type.
|
/// Created by calling the [`customize`](Responder::customize) method on a [`Responder`] type.
|
||||||
pub struct CustomizeResponder<R> {
|
pub struct CustomizeResponder<R> {
|
||||||
|
@ -137,6 +137,29 @@ impl<R: Responder> CustomizeResponder<R> {
|
||||||
Some(&mut self.inner)
|
Some(&mut self.inner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Appends a `cookie` to the final response.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Final response will be an error if `cookie` cannot be converted into a valid header value.
|
||||||
|
#[cfg(feature = "cookies")]
|
||||||
|
pub fn add_cookie(mut self, cookie: &crate::cookie::Cookie<'_>) -> Self {
|
||||||
|
use actix_http::header::{TryIntoHeaderValue as _, SET_COOKIE};
|
||||||
|
|
||||||
|
if let Some(inner) = self.inner() {
|
||||||
|
match cookie.to_string().try_into_value() {
|
||||||
|
Ok(val) => {
|
||||||
|
inner.append_headers.append(SET_COOKIE, val);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
self.error = Some(err.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Responder for CustomizeResponder<T>
|
impl<T> Responder for CustomizeResponder<T>
|
||||||
|
@ -175,6 +198,7 @@ mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
cookie::Cookie,
|
||||||
http::header::{HeaderValue, CONTENT_TYPE},
|
http::header::{HeaderValue, CONTENT_TYPE},
|
||||||
test::TestRequest,
|
test::TestRequest,
|
||||||
};
|
};
|
||||||
|
@ -209,6 +233,22 @@ mod tests {
|
||||||
to_bytes(res.into_body()).await.unwrap(),
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
Bytes::from_static(b"test"),
|
Bytes::from_static(b"test"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let res = "test"
|
||||||
|
.to_string()
|
||||||
|
.customize()
|
||||||
|
.add_cookie(&Cookie::new("name", "value"))
|
||||||
|
.respond_to(&req);
|
||||||
|
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
assert_eq!(
|
||||||
|
res.cookies().collect::<Vec<Cookie<'_>>>(),
|
||||||
|
vec![Cookie::new("name", "value")],
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
to_bytes(res.into_body()).await.unwrap(),
|
||||||
|
Bytes::from_static(b"test"),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
|
Loading…
Reference in New Issue