1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-29 03:25:28 +02:00

Compare commits

...

19 Commits

Author SHA1 Message Date
Rob Ede
f3f41a0cc7 prepare actix-http release 3.2.1 2022-07-02 16:50:54 +01:00
Rob Ede
987067698b use sparse registry in CI 2022-07-01 12:45:26 +01:00
Rob Ede
b62f1b4ef7 migrate deprecated method in docs 2022-07-01 12:40:00 +01:00
Rob Ede
df5257c373 update trust dns resolver 2022-07-01 10:21:46 +01:00
Rob Ede
226ea696ce update dev deps 2022-07-01 10:19:28 +01:00
Rob Ede
e524fc86ea add HTTP/0.9 rejection test 2022-07-01 09:03:57 +01:00
Rob Ede
7e990e423f add http/1.0 GET parsing tests 2022-07-01 08:24:45 +01:00
Rob Ede
8f9a12ed5d fix parsing ambiguities for HTTP/1.0 requests (#2794)
* fix HRS vuln when first CL header is 0

* ignore TE headers in http/1.0 reqs

* update changelog

* disallow HTTP/1.0 requests without a CL header

* fix test

* broken fix for http1.0 post requests
2022-07-01 08:23:40 +01:00
Rob Ede
c6eba2da9b prepare actix-http release 3.2.0 (#2801) 2022-07-01 06:16:17 +01:00
Rob Ede
06c7945801 retain previously set vary headers when using compress (#2798)
* retain previously set vary headers when using compress
2022-06-30 09:19:16 +01:00
Ulf Lilleengen
0dba6310c6 Expose option for setting TLS handshake timeout (#2752)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-06-27 02:57:21 +00:00
Rob Ede
f7d7d92984 address clippy lints 2022-06-27 03:12:36 +01:00
nerix
3d6ea7fe9b Improve documentation for actix-web-actors (#2788) 2022-06-26 16:45:02 +00:00
PeterPierinakos
8dbf7da89f Fix common grammar mistakes and add small documentation for AppConfig's Default implementation (#2793) 2022-06-25 14:01:06 +00:00
oatoam
de92b3be2e fix unrecoverable Err(Overflow) in websocket frame parser (#2790) 2022-06-24 03:46:17 +00:00
e-rhodes
5d0e8138ee Add getters for &ServiceRequest (#2786) 2022-06-22 21:02:03 +01:00
Yuki Okushi
6b7196225e Bump up MSRV to 1.57 (#2789) 2022-06-22 12:08:06 +01:00
Isabel Atkinson
265fa0d050 Add link to MongoDB example in README (#2783) 2022-06-15 22:38:10 +01:00
Yuki Okushi
062127a210 Revert "actix-http: Pull actix-web dev-dep from Git repo"
This reverts commit 3926416580.
2022-06-12 00:55:06 +09:00
52 changed files with 714 additions and 113 deletions

View File

@@ -23,6 +23,7 @@ jobs:
CI: 1 CI: 1
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
VCPKGRS_DYNAMIC: 1 VCPKGRS_DYNAMIC: 1
CARGO_UNSTABLE_SPARSE_REGISTRY: true
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -86,6 +87,11 @@ jobs:
ci_feature_powerset_check: ci_feature_powerset_check:
name: Verify Feature Combinations name: Verify Feature Combinations
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CI: 1
CARGO_INCREMENTAL: 0
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -119,6 +125,11 @@ jobs:
nextest: nextest:
name: nextest name: nextest
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CI: 1
CARGO_INCREMENTAL: 0
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@@ -16,7 +16,7 @@ jobs:
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } - { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc }
version: version:
- 1.56.0 # MSRV - 1.57.0 # MSRV
- stable - stable
name: ${{ matrix.target.name }} / ${{ matrix.version }} name: ${{ matrix.target.name }} / ${{ matrix.version }}

View File

@@ -1,6 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2022-xx-xx
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
## 0.6.1 - 2022-06-11 ## 0.6.1 - 2022-06-11

View File

@@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.1)](https://docs.rs/actix-files/0.6.1) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.1)](https://docs.rs/actix-files/0.6.1)
![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg)
![License](https://img.shields.io/crates/l/actix-files.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-files/0.6.1/status.svg)](https://deps.rs/crate/actix-files/0.6.1) [![dependency status](https://deps.rs/crate/actix-files/0.6.1/status.svg)](https://deps.rs/crate/actix-files/0.6.1)

View File

@@ -1,7 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2022-xx-xx
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
## 3.0.0-beta.13 - 2022-02-16 ## 3.0.0-beta.13 - 2022-02-16

View File

@@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http-test/3.0.0-beta.13) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http-test/3.0.0-beta.13)
![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
<br> <br>
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.13) [![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.13)

View File

@@ -1,6 +1,25 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2022-xx-xx
## 3.2.1 - 2022-07-02
### Fixed
- Fix parsing ambiguity in Transfer-Encoding and Content-Length headers for HTTP/1.0 requests. [#2794]
[#2794]: https://github.com/actix/actix-web/pull/2794
## 3.2.0 - 2022-06-30
### Changed
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
### Fixed
- Websocket parser no longer throws endless overflow errors after receiving an oversized frame. [#2790]
- Retain previously set Vary headers when using compression encoder. [#2798]
[#2790]: https://github.com/actix/actix-web/pull/2790
[#2798]: https://github.com/actix/actix-web/pull/2798
## 3.1.0 - 2022-06-11 ## 3.1.0 - 2022-06-11

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "3.1.0" version = "3.2.1"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",
@@ -100,7 +100,7 @@ zstd = { version = "0.11", optional = true }
actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] }
actix-server = "2" actix-server = "2"
actix-tls = { version = "3", features = ["openssl"] } actix-tls = { version = "3", features = ["openssl"] }
actix-web = { git = "https://github.com/actix/actix-web", rev = "43671ae4aaf2c20150fd1e5ca54055eb5d114273" } actix-web = "4"
async-stream = "0.3" async-stream = "0.3"
criterion = { version = "0.3", features = ["html_reports"] } criterion = { version = "0.3", features = ["html_reports"] }
@@ -108,10 +108,10 @@ env_logger = "0.9"
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
memchr = "2.4" memchr = "2.4"
once_cell = "1.9" once_cell = "1.9"
rcgen = "0.8" rcgen = "0.9"
regex = "1.3" regex = "1.3"
rustversion = "1" rustversion = "1"
rustls-pemfile = "0.2" rustls-pemfile = "1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
static_assertions = "1" static_assertions = "1"

View File

@@ -3,11 +3,11 @@
> HTTP primitives for the Actix ecosystem. > HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![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.1.0)](https://docs.rs/actix-http/3.1.0) [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.2.1)](https://docs.rs/actix-http/3.2.1)
![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-http/3.1.0/status.svg)](https://deps.rs/crate/actix-http/3.1.0) [![dependency status](https://deps.rs/crate/actix-http/3.2.1/status.svg)](https://deps.rs/crate/actix-http/3.2.1)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![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) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@@ -481,6 +481,7 @@ mod tests {
assert_poll_next_none!(pl); assert_poll_next_none!(pl);
} }
#[allow(clippy::let_unit_value)]
#[actix_rt::test] #[actix_rt::test]
async fn test_unit() { async fn test_unit() {
let pl = (); let pl = ();

View File

@@ -257,7 +257,7 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
head.headers_mut() head.headers_mut()
.insert(header::CONTENT_ENCODING, encoding.to_header_value()); .insert(header::CONTENT_ENCODING, encoding.to_header_value());
head.headers_mut() head.headers_mut()
.insert(header::VARY, HeaderValue::from_static("accept-encoding")); .append(header::VARY, HeaderValue::from_static("accept-encoding"));
head.no_chunking(false); head.no_chunking(false);
} }

View File

@@ -46,6 +46,23 @@ pub(crate) enum PayloadLength {
None, None,
} }
impl PayloadLength {
/// Returns true if variant is `None`.
fn is_none(&self) -> bool {
matches!(self, Self::None)
}
/// Returns true if variant is represents zero-length (not none) payload.
fn is_zero(&self) -> bool {
matches!(
self,
PayloadLength::Payload(PayloadType::Payload(PayloadDecoder {
kind: Kind::Length(0)
}))
)
}
}
pub(crate) trait MessageType: Sized { pub(crate) trait MessageType: Sized {
fn set_connection_type(&mut self, conn_type: Option<ConnectionType>); fn set_connection_type(&mut self, conn_type: Option<ConnectionType>);
@@ -59,6 +76,7 @@ pub(crate) trait MessageType: Sized {
&mut self, &mut self,
slice: &Bytes, slice: &Bytes,
raw_headers: &[HeaderIndex], raw_headers: &[HeaderIndex],
version: Version,
) -> Result<PayloadLength, ParseError> { ) -> Result<PayloadLength, ParseError> {
let mut ka = None; let mut ka = None;
let mut has_upgrade_websocket = false; let mut has_upgrade_websocket = false;
@@ -87,21 +105,23 @@ pub(crate) trait MessageType: Sized {
return Err(ParseError::Header); return Err(ParseError::Header);
} }
header::CONTENT_LENGTH => match value.to_str() { header::CONTENT_LENGTH => match value.to_str().map(str::trim) {
Ok(s) if s.trim().starts_with('+') => { Ok(val) if val.starts_with('+') => {
debug!("illegal Content-Length: {:?}", s); debug!("illegal Content-Length: {:?}", val);
return Err(ParseError::Header); return Err(ParseError::Header);
} }
Ok(s) => {
if let Ok(len) = s.parse::<u64>() { Ok(val) => {
if len != 0 { if let Ok(len) = val.parse::<u64>() {
// accept 0 lengths here and remove them in `decode` after all
// headers have been processed to prevent request smuggling issues
content_length = Some(len); content_length = Some(len);
}
} else { } else {
debug!("illegal Content-Length: {:?}", s); debug!("illegal Content-Length: {:?}", val);
return Err(ParseError::Header); return Err(ParseError::Header);
} }
} }
Err(_) => { Err(_) => {
debug!("illegal Content-Length: {:?}", value); debug!("illegal Content-Length: {:?}", value);
return Err(ParseError::Header); return Err(ParseError::Header);
@@ -114,22 +134,23 @@ pub(crate) trait MessageType: Sized {
return Err(ParseError::Header); return Err(ParseError::Header);
} }
header::TRANSFER_ENCODING => { header::TRANSFER_ENCODING if version == Version::HTTP_11 => {
seen_te = true; seen_te = true;
if let Ok(s) = value.to_str().map(str::trim) { if let Ok(val) = value.to_str().map(str::trim) {
if s.eq_ignore_ascii_case("chunked") { if val.eq_ignore_ascii_case("chunked") {
chunked = true; chunked = true;
} else if s.eq_ignore_ascii_case("identity") { } else if val.eq_ignore_ascii_case("identity") {
// allow silently since multiple TE headers are already checked // allow silently since multiple TE headers are already checked
} else { } else {
debug!("illegal Transfer-Encoding: {:?}", s); debug!("illegal Transfer-Encoding: {:?}", val);
return Err(ParseError::Header); return Err(ParseError::Header);
} }
} else { } else {
return Err(ParseError::Header); return Err(ParseError::Header);
} }
} }
// connection keep-alive state // connection keep-alive state
header::CONNECTION => { header::CONNECTION => {
ka = if let Ok(conn) = value.to_str().map(str::trim) { ka = if let Ok(conn) = value.to_str().map(str::trim) {
@@ -146,6 +167,7 @@ pub(crate) trait MessageType: Sized {
None None
}; };
} }
header::UPGRADE => { header::UPGRADE => {
if let Ok(val) = value.to_str().map(str::trim) { if let Ok(val) = value.to_str().map(str::trim) {
if val.eq_ignore_ascii_case("websocket") { if val.eq_ignore_ascii_case("websocket") {
@@ -153,19 +175,23 @@ pub(crate) trait MessageType: Sized {
} }
} }
} }
header::EXPECT => { header::EXPECT => {
let bytes = value.as_bytes(); let bytes = value.as_bytes();
if bytes.len() >= 4 && &bytes[0..4] == b"100-" { if bytes.len() >= 4 && &bytes[0..4] == b"100-" {
expect = true; expect = true;
} }
} }
_ => {} _ => {}
} }
headers.append(name, value); headers.append(name, value);
} }
} }
self.set_connection_type(ka); self.set_connection_type(ka);
if expect { if expect {
self.set_expect() self.set_expect()
} }
@@ -249,7 +275,22 @@ impl MessageType for Request {
let mut msg = Request::new(); let mut msg = Request::new();
// convert headers // convert headers
let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; let mut length =
msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?;
// disallow HTTP/1.0 POST requests that do not contain a Content-Length headers
// see https://datatracker.ietf.org/doc/html/rfc1945#section-7.2.2
if ver == Version::HTTP_10 && method == Method::POST && length.is_none() {
debug!("no Content-Length specified for HTTP/1.0 POST request");
return Err(ParseError::Header);
}
// Remove CL value if 0 now that all headers and HTTP/1.0 special cases are processed.
// Protects against some request smuggling attacks.
// See https://github.com/actix/actix-web/issues/2767.
if length.is_zero() {
length = PayloadLength::None;
}
// payload decoder // payload decoder
let decoder = match length { let decoder = match length {
@@ -337,7 +378,15 @@ impl MessageType for ResponseHead {
msg.version = ver; msg.version = ver;
// convert headers // convert headers
let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; let mut length =
msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len], ver)?;
// Remove CL value if 0 now that all headers and HTTP/1.0 special cases are processed.
// Protects against some request smuggling attacks.
// See https://github.com/actix/actix-web/issues/2767.
if length.is_zero() {
length = PayloadLength::None;
}
// message payload // message payload
let decoder = if let PayloadLength::Payload(pl) = length { let decoder = if let PayloadLength::Payload(pl) = length {
@@ -606,14 +655,100 @@ mod tests {
} }
#[test] #[test]
fn test_parse_post() { fn parse_h09_reject() {
let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); let mut buf = BytesMut::from(
"GET /test1 HTTP/0.9\r\n\
\r\n",
);
let mut reader = MessageDecoder::<Request>::default();
reader.decode(&mut buf).unwrap_err();
let mut buf = BytesMut::from(
"POST /test2 HTTP/0.9\r\n\
Content-Length: 3\r\n\
\r\n
abc",
);
let mut reader = MessageDecoder::<Request>::default();
reader.decode(&mut buf).unwrap_err();
}
#[test]
fn parse_h10_get() {
let mut buf = BytesMut::from(
"GET /test1 HTTP/1.0\r\n\
\r\n",
);
let mut reader = MessageDecoder::<Request>::default();
let (req, _) = reader.decode(&mut buf).unwrap().unwrap();
assert_eq!(req.version(), Version::HTTP_10);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test1");
let mut buf = BytesMut::from(
"GET /test2 HTTP/1.0\r\n\
Content-Length: 0\r\n\
\r\n",
);
let mut reader = MessageDecoder::<Request>::default();
let (req, _) = reader.decode(&mut buf).unwrap().unwrap();
assert_eq!(req.version(), Version::HTTP_10);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test2");
let mut buf = BytesMut::from(
"GET /test3 HTTP/1.0\r\n\
Content-Length: 3\r\n\
\r\n
abc",
);
let mut reader = MessageDecoder::<Request>::default();
let (req, _) = reader.decode(&mut buf).unwrap().unwrap();
assert_eq!(req.version(), Version::HTTP_10);
assert_eq!(*req.method(), Method::GET);
assert_eq!(req.path(), "/test3");
}
#[test]
fn parse_h10_post() {
let mut buf = BytesMut::from(
"POST /test1 HTTP/1.0\r\n\
Content-Length: 3\r\n\
\r\n\
abc",
);
let mut reader = MessageDecoder::<Request>::default();
let (req, _) = reader.decode(&mut buf).unwrap().unwrap();
assert_eq!(req.version(), Version::HTTP_10);
assert_eq!(*req.method(), Method::POST);
assert_eq!(req.path(), "/test1");
let mut buf = BytesMut::from(
"POST /test2 HTTP/1.0\r\n\
Content-Length: 0\r\n\
\r\n",
);
let mut reader = MessageDecoder::<Request>::default(); let mut reader = MessageDecoder::<Request>::default();
let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); let (req, _) = reader.decode(&mut buf).unwrap().unwrap();
assert_eq!(req.version(), Version::HTTP_10); assert_eq!(req.version(), Version::HTTP_10);
assert_eq!(*req.method(), Method::POST); assert_eq!(*req.method(), Method::POST);
assert_eq!(req.path(), "/test2"); assert_eq!(req.path(), "/test2");
let mut buf = BytesMut::from(
"POST /test3 HTTP/1.0\r\n\
\r\n",
);
let mut reader = MessageDecoder::<Request>::default();
let err = reader.decode(&mut buf).unwrap_err();
assert!(err.to_string().contains("Header"))
} }
#[test] #[test]
@@ -981,6 +1116,17 @@ mod tests {
); );
expect_parse_err!(&mut buf); expect_parse_err!(&mut buf);
let mut buf = BytesMut::from(
"GET / HTTP/1.1\r\n\
Host: example.com\r\n\
Content-Length: 0\r\n\
Content-Length: 2\r\n\
\r\n\
ab",
);
expect_parse_err!(&mut buf);
} }
#[test] #[test]
@@ -996,6 +1142,40 @@ mod tests {
expect_parse_err!(&mut buf); expect_parse_err!(&mut buf);
} }
#[test]
fn hrs_te_http10() {
// in HTTP/1.0 transfer encoding is ignored and must therefore contain a CL header
let mut buf = BytesMut::from(
"POST / HTTP/1.0\r\n\
Host: example.com\r\n\
Transfer-Encoding: chunked\r\n\
\r\n\
3\r\n\
aaa\r\n\
0\r\n\
",
);
expect_parse_err!(&mut buf);
}
#[test]
fn hrs_cl_and_te_http10() {
// in HTTP/1.0 transfer encoding is simply ignored so it's fine to have both
let mut buf = BytesMut::from(
"GET / HTTP/1.0\r\n\
Host: example.com\r\n\
Content-Length: 3\r\n\
Transfer-Encoding: chunked\r\n\
\r\n\
000",
);
parse_ready!(&mut buf);
}
#[test] #[test]
fn hrs_unknown_transfer_encoding() { fn hrs_unknown_transfer_encoding() {
let mut buf = BytesMut::from( let mut buf = BytesMut::from(

View File

@@ -25,6 +25,7 @@
)] )]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub use ::http::{uri, uri::Uri}; pub use ::http::{uri, uri::Uri};
pub use ::http::{Method, StatusCode, Version}; pub use ::http::{Method, StatusCode, Version};
@@ -69,6 +70,8 @@ pub use self::payload::{BoxedPayloadStream, Payload, PayloadStream};
pub use self::requests::{Request, RequestHead, RequestHeadType}; pub use self::requests::{Request, RequestHead, RequestHeadType};
pub use self::responses::{Response, ResponseBuilder, ResponseHead}; pub use self::responses::{Response, ResponseBuilder, ResponseHead};
pub use self::service::HttpService; pub use self::service::HttpService;
#[cfg(any(feature = "openssl", feature = "rustls"))]
pub use self::service::TlsAcceptorConfig;
/// A major HTTP protocol version. /// A major HTTP protocol version.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]

View File

@@ -237,7 +237,7 @@ mod tests {
.await; .await;
let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
let _ = stream stream
.write_all(b"GET /camel HTTP/1.1\r\nConnection: Close\r\n\r\n") .write_all(b"GET /camel HTTP/1.1\r\nConnection: Close\r\n\r\n")
.unwrap(); .unwrap();
let mut data = vec![]; let mut data = vec![];
@@ -251,7 +251,7 @@ mod tests {
assert!(memmem::find(&data, b"content-length").is_none()); assert!(memmem::find(&data, b"content-length").is_none());
let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
let _ = stream stream
.write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n") .write_all(b"GET /lower HTTP/1.1\r\nConnection: Close\r\n\r\n")
.unwrap(); .unwrap();
let mut data = vec![]; let mut data = vec![];

View File

@@ -181,6 +181,25 @@ where
} }
} }
/// Configuration options used when accepting TLS connection.
#[cfg(any(feature = "openssl", feature = "rustls"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "rustls"))))]
#[derive(Debug, Default)]
pub struct TlsAcceptorConfig {
pub(crate) handshake_timeout: Option<std::time::Duration>,
}
#[cfg(any(feature = "openssl", feature = "rustls"))]
impl TlsAcceptorConfig {
/// Set TLS handshake timeout duration.
pub fn handshake_timeout(self, dur: std::time::Duration) -> Self {
Self {
handshake_timeout: Some(dur),
// ..self
}
}
}
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
mod openssl { mod openssl {
use actix_service::ServiceFactoryExt as _; use actix_service::ServiceFactoryExt as _;
@@ -230,7 +249,28 @@ mod openssl {
Error = TlsError<SslError, DispatchError>, Error = TlsError<SslError, DispatchError>,
InitError = (), InitError = (),
> { > {
Acceptor::new(acceptor) self.openssl_with_config(acceptor, TlsAcceptorConfig::default())
}
/// Create OpenSSL based service with custom TLS acceptor configuration.
pub fn openssl_with_config(
self,
acceptor: SslAcceptor,
tls_acceptor_config: TlsAcceptorConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<SslError, DispatchError>,
InitError = (),
> {
let mut acceptor = Acceptor::new(acceptor);
if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
acceptor.set_handshake_timeout(handshake_timeout);
}
acceptor
.map_init_err(|_| { .map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init") unreachable!("TLS acceptor service factory does not error on init")
}) })
@@ -293,8 +333,23 @@ mod rustls {
{ {
/// Create Rustls based service. /// Create Rustls based service.
pub fn rustls( pub fn rustls(
self,
config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = (),
> {
self.rustls_with_config(config, TlsAcceptorConfig::default())
}
/// Create Rustls based service with custom TLS acceptor configuration.
pub fn rustls_with_config(
self, self,
mut config: ServerConfig, mut config: ServerConfig,
tls_acceptor_config: TlsAcceptorConfig,
) -> impl ServiceFactory< ) -> impl ServiceFactory<
TcpStream, TcpStream,
Config = (), Config = (),
@@ -306,7 +361,13 @@ mod rustls {
protos.extend_from_slice(&config.alpn_protocols); protos.extend_from_slice(&config.alpn_protocols);
config.alpn_protocols = protos; config.alpn_protocols = protos;
Acceptor::new(config) let mut acceptor = Acceptor::new(config);
if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
acceptor.set_handshake_timeout(handshake_timeout);
}
acceptor
.map_init_err(|_| { .map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init") unreachable!("TLS acceptor service factory does not error on init")
}) })

View File

@@ -17,7 +17,6 @@ impl Parser {
fn parse_metadata( fn parse_metadata(
src: &[u8], src: &[u8],
server: bool, server: bool,
max_size: usize,
) -> Result<Option<(usize, bool, OpCode, usize, Option<[u8; 4]>)>, ProtocolError> { ) -> Result<Option<(usize, bool, OpCode, usize, Option<[u8; 4]>)>, ProtocolError> {
let chunk_len = src.len(); let chunk_len = src.len();
@@ -60,20 +59,12 @@ impl Parser {
return Ok(None); return Ok(None);
} }
let len = u64::from_be_bytes(TryFrom::try_from(&src[idx..idx + 8]).unwrap()); let len = u64::from_be_bytes(TryFrom::try_from(&src[idx..idx + 8]).unwrap());
if len > max_size as u64 {
return Err(ProtocolError::Overflow);
}
idx += 8; idx += 8;
len as usize len as usize
} else { } else {
len as usize len as usize
}; };
// check for max allowed size
if length > max_size {
return Err(ProtocolError::Overflow);
}
let mask = if server { let mask = if server {
if chunk_len < idx + 4 { if chunk_len < idx + 4 {
return Ok(None); return Ok(None);
@@ -98,8 +89,7 @@ impl Parser {
max_size: usize, max_size: usize,
) -> Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError> { ) -> Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError> {
// try to parse ws frame metadata // try to parse ws frame metadata
let (idx, finished, opcode, length, mask) = let (idx, finished, opcode, length, mask) = match Parser::parse_metadata(src, server)? {
match Parser::parse_metadata(src, server, max_size)? {
None => return Ok(None), None => return Ok(None),
Some(res) => res, Some(res) => res,
}; };
@@ -112,6 +102,13 @@ impl Parser {
// remove prefix // remove prefix
src.advance(idx); src.advance(idx);
// check for max allowed size
if length > max_size {
// drop the payload
src.advance(length);
return Err(ProtocolError::Overflow);
}
// no need for body // no need for body
if length == 0 { if length == 0 {
return Ok(Some((finished, opcode, None))); return Ok(Some((finished, opcode, None)));
@@ -339,6 +336,30 @@ mod tests {
} }
} }
#[test]
fn test_parse_frame_max_size_recoverability() {
let mut buf = BytesMut::new();
// The first text frame with length == 2, payload doesn't matter.
buf.extend(&[0b0000_0001u8, 0b0000_0010u8, 0b0000_0000u8, 0b0000_0000u8]);
// Next binary frame with length == 2 and payload == `[0x1111_1111u8, 0x1111_1111u8]`.
buf.extend(&[0b0000_0010u8, 0b0000_0010u8, 0b1111_1111u8, 0b1111_1111u8]);
assert_eq!(buf.len(), 8);
assert!(matches!(
Parser::parse(&mut buf, false, 1),
Err(ProtocolError::Overflow)
));
assert_eq!(buf.len(), 4);
let frame = extract(Parser::parse(&mut buf, false, 2));
assert!(!frame.finished);
assert_eq!(frame.opcode, OpCode::Binary);
assert_eq!(
frame.payload,
Bytes::from(vec![0b1111_1111u8, 0b1111_1111u8])
);
assert_eq!(buf.len(), 0);
}
#[test] #[test]
fn test_ping_frame() { fn test_ping_frame() {
let mut buf = BytesMut::new(); let mut buf = BytesMut::new();

View File

@@ -2,13 +2,13 @@
extern crate tls_openssl as openssl; extern crate tls_openssl as openssl;
use std::{convert::Infallible, io}; use std::{convert::Infallible, io, time::Duration};
use actix_http::{ use actix_http::{
body::{BodyStream, BoxBody, SizedStream}, body::{BodyStream, BoxBody, SizedStream},
error::PayloadError, error::PayloadError,
header::{self, HeaderValue}, header::{self, HeaderValue},
Error, HttpService, Method, Request, Response, StatusCode, Version, Error, HttpService, Method, Request, Response, StatusCode, TlsAcceptorConfig, Version,
}; };
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{fn_service, ServiceFactoryExt}; use actix_service::{fn_service, ServiceFactoryExt};
@@ -89,7 +89,10 @@ async fn h2_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_2); assert_eq!(req.version(), Version::HTTP_2);
ok::<_, Error>(Response::ok()) ok::<_, Error>(Response::ok())
}) })
.openssl(tls_config()) .openssl_with_config(
tls_config(),
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
)
.map_err(|_| ()) .map_err(|_| ())
}) })
.await; .await;

View File

@@ -8,13 +8,14 @@ use std::{
net::{SocketAddr, TcpStream as StdTcpStream}, net::{SocketAddr, TcpStream as StdTcpStream},
sync::Arc, sync::Arc,
task::Poll, task::Poll,
time::Duration,
}; };
use actix_http::{ use actix_http::{
body::{BodyStream, BoxBody, SizedStream}, body::{BodyStream, BoxBody, SizedStream},
error::PayloadError, error::PayloadError,
header::{self, HeaderName, HeaderValue}, header::{self, HeaderName, HeaderValue},
Error, HttpService, Method, Request, Response, StatusCode, Version, Error, HttpService, Method, Request, Response, StatusCode, TlsAcceptorConfig, Version,
}; };
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_rt::pin; use actix_rt::pin;
@@ -160,7 +161,10 @@ async fn h2_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_2); assert_eq!(req.version(), Version::HTTP_2);
ok::<_, Error>(Response::ok()) ok::<_, Error>(Response::ok())
}) })
.rustls(tls_config()) .rustls_with_config(
tls_config(),
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
)
}) })
.await; .await;

View File

@@ -1,7 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2022-xx-xx
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
## 0.4.0 - 2022-02-25 ## 0.4.0 - 2022-02-25

View File

@@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart)
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0)](https://docs.rs/actix-multipart/0.4.0) [![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0)](https://docs.rs/actix-multipart/0.4.0)
![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0) [![dependency status](https://deps.rs/crate/actix-multipart/0.4.0/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0)

View File

@@ -1,7 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2022-xx-xx
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
## 0.5.0 - 2022-02-22 ## 0.5.0 - 2022-02-22

View File

@@ -649,7 +649,7 @@ impl ResourceDef {
/// resource.capture_match_info_fn( /// resource.capture_match_info_fn(
/// path, /// path,
/// // when env var is not set, reject when path contains "admin" /// // when env var is not set, reject when path contains "admin"
/// |res| !(!admin_allowed && res.path().contains("admin")), /// |path| !(!admin_allowed && path.as_str().contains("admin")),
/// ) /// )
/// } /// }
/// ///

View File

@@ -1,7 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2022-xx-xx
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
## 0.1.0-beta.13 - 2022-02-16 ## 0.1.0-beta.13 - 2022-02-16

View File

@@ -1,7 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2022-xx-xx
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
## 4.1.0 - 2022-03-02 ## 4.1.0 - 2022-03-02

View File

@@ -29,6 +29,9 @@ tokio = { version = "1.13.1", features = ["sync"] }
actix-rt = "2.2" actix-rt = "2.2"
actix-test = "0.1.0-beta.13" actix-test = "0.1.0-beta.13"
awc = { version = "3", default-features = false } awc = { version = "3", default-features = false }
actix-web = { version = "4", features = ["macros"] }
mime = "0.3"
env_logger = "0.9" env_logger = "0.9"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }

View File

@@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.1.0)](https://docs.rs/actix-web-actors/4.1.0) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.1.0)](https://docs.rs/actix-web-actors/4.1.0)
![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg)
![License](https://img.shields.io/crates/l/actix-web-actors.svg) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-web-actors/4.1.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.1.0) [![dependency status](https://deps.rs/crate/actix-web-actors/4.1.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.1.0)

View File

@@ -14,6 +14,58 @@ use futures_core::Stream;
use tokio::sync::oneshot::Sender; use tokio::sync::oneshot::Sender;
/// Execution context for HTTP actors /// Execution context for HTTP actors
///
/// # Example
///
/// A demonstration of [server-sent events](https://developer.mozilla.org/docs/Web/API/Server-sent_events) using actors:
///
/// ```no_run
/// use std::time::Duration;
///
/// use actix::{Actor, AsyncContext};
/// use actix_web::{get, http::header, App, HttpResponse, HttpServer};
/// use actix_web_actors::HttpContext;
/// use bytes::Bytes;
///
/// struct MyActor {
/// count: usize,
/// }
///
/// impl Actor for MyActor {
/// type Context = HttpContext<Self>;
///
/// fn started(&mut self, ctx: &mut Self::Context) {
/// ctx.run_later(Duration::from_millis(100), Self::write);
/// }
/// }
///
/// impl MyActor {
/// fn write(&mut self, ctx: &mut HttpContext<Self>) {
/// self.count += 1;
/// if self.count > 3 {
/// ctx.write_eof()
/// } else {
/// ctx.write(Bytes::from(format!("event: count\ndata: {}\n\n", self.count)));
/// ctx.run_later(Duration::from_millis(100), Self::write);
/// }
/// }
/// }
///
/// #[get("/")]
/// async fn index() -> HttpResponse {
/// HttpResponse::Ok()
/// .insert_header(header::ContentType(mime::TEXT_EVENT_STREAM))
/// .streaming(HttpContext::create(MyActor { count: 0 }))
/// }
///
/// #[actix_web::main]
/// async fn main() -> std::io::Result<()> {
/// HttpServer::new(|| App::new().service(index))
/// .bind(("127.0.0.1", 8080))?
/// .run()
/// .await
/// }
/// ```
pub struct HttpContext<A> pub struct HttpContext<A>
where where
A: Actor<Context = HttpContext<A>>, A: Actor<Context = HttpContext<A>>,
@@ -210,7 +262,7 @@ mod tests {
type Context = HttpContext<Self>; type Context = HttpContext<Self>;
fn started(&mut self, ctx: &mut Self::Context) { fn started(&mut self, ctx: &mut Self::Context) {
ctx.run_later(Duration::from_millis(100), |slf, ctx| slf.write(ctx)); ctx.run_later(Duration::from_millis(100), Self::write);
} }
} }
@@ -221,7 +273,7 @@ mod tests {
ctx.write_eof() ctx.write_eof()
} else { } else {
ctx.write(Bytes::from(format!("LINE-{}", self.count))); ctx.write(Bytes::from(format!("LINE-{}", self.count)));
ctx.run_later(Duration::from_millis(100), |slf, ctx| slf.write(ctx)); ctx.run_later(Duration::from_millis(100), Self::write);
} }
} }
} }

View File

@@ -1,4 +1,59 @@
//! Actix actors support for Actix Web. //! Actix actors support for Actix Web.
//!
//! # Examples
//!
//! ```no_run
//! use actix::{Actor, StreamHandler};
//! use actix_web::{get, web, App, Error, HttpRequest, HttpResponse, HttpServer};
//! use actix_web_actors::ws;
//!
//! /// Define Websocket actor
//! struct MyWs;
//!
//! impl Actor for MyWs {
//! type Context = ws::WebsocketContext<Self>;
//! }
//!
//! /// Handler for ws::Message message
//! impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWs {
//! fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
//! match msg {
//! Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
//! Ok(ws::Message::Text(text)) => ctx.text(text),
//! Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
//! _ => (),
//! }
//! }
//! }
//!
//! #[get("/ws")]
//! async fn index(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
//! ws::start(MyWs, &req, stream)
//! }
//!
//! #[actix_web::main]
//! async fn main() -> std::io::Result<()> {
//! HttpServer::new(|| App::new().service(index))
//! .bind(("127.0.0.1", 8080))?
//! .run()
//! .await
//! }
//! ```
//!
//! # Documentation & Community Resources
//! In addition to this API documentation, several other resources are available:
//!
//! * [Website & User Guide](https://actix.rs/)
//! * [Documentation for `actix_web`](actix_web)
//! * [Examples Repository](https://github.com/actix/examples)
//! * [Community Chat on Discord](https://discord.gg/NWpN5mmg3x)
//!
//! To get started navigating the API docs, you may consider looking at the following pages first:
//!
//! * [`ws`]: This module provides actor support for WebSockets.
//!
//! * [`HttpContext`]: This struct provides actor support for streaming HTTP responses.
//!
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)] #![warn(future_incompatible)]

View File

@@ -1,4 +1,60 @@
//! Websocket integration. //! Websocket integration.
//!
//! # Examples
//!
//! ```no_run
//! use actix::{Actor, StreamHandler};
//! use actix_web::{get, web, App, Error, HttpRequest, HttpResponse, HttpServer};
//! use actix_web_actors::ws;
//!
//! /// Define Websocket actor
//! struct MyWs;
//!
//! impl Actor for MyWs {
//! type Context = ws::WebsocketContext<Self>;
//! }
//!
//! /// Handler for ws::Message message
//! impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWs {
//! fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
//! match msg {
//! Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
//! Ok(ws::Message::Text(text)) => ctx.text(text),
//! Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
//! _ => (),
//! }
//! }
//! }
//!
//! #[get("/ws")]
//! async fn websocket(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
//! ws::start(MyWs, &req, stream)
//! }
//!
//! const MAX_FRAME_SIZE: usize = 16_384; // 16KiB
//!
//! #[get("/custom-ws")]
//! async fn custom_websocket(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
//! // Create a Websocket session with a specific max frame size, and protocols.
//! ws::WsResponseBuilder::new(MyWs, &req, stream)
//! .frame_size(MAX_FRAME_SIZE)
//! .protocols(&["A", "B"])
//! .start()
//! }
//!
//! #[actix_web::main]
//! async fn main() -> std::io::Result<()> {
//! HttpServer::new(|| {
//! App::new()
//! .service(websocket)
//! .service(custom_websocket)
//! })
//! .bind(("127.0.0.1", 8080))?
//! .run()
//! .await
//! }
//! ```
//!
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
@@ -41,20 +97,51 @@ use tokio::sync::oneshot;
/// ///
/// # Examples /// # Examples
/// ///
/// Create a Websocket session response with default configuration. /// ```no_run
/// ```ignore /// # use actix::{Actor, StreamHandler};
/// WsResponseBuilder::new(WsActor, &req, stream).start() /// # use actix_web::{get, web, App, Error, HttpRequest, HttpResponse, HttpServer};
/// ``` /// # use actix_web_actors::ws;
/// #
/// # struct MyWs;
/// #
/// # impl Actor for MyWs {
/// # type Context = ws::WebsocketContext<Self>;
/// # }
/// #
/// # /// Handler for ws::Message message
/// # impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWs {
/// # fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {}
/// # }
/// #
/// #[get("/ws")]
/// async fn websocket(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
/// ws::WsResponseBuilder::new(MyWs, &req, stream).start()
/// }
/// ///
/// Create a Websocket session with a specific max frame size, [`Codec`], and protocols.
/// ```ignore
/// const MAX_FRAME_SIZE: usize = 16_384; // 16KiB /// const MAX_FRAME_SIZE: usize = 16_384; // 16KiB
/// ///
/// ws::WsResponseBuilder::new(WsActor, &req, stream) /// #[get("/custom-ws")]
/// .codec(Codec::new()) /// async fn custom_websocket(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
/// .protocols(&["A", "B"]) /// // Create a Websocket session with a specific max frame size, codec, and protocols.
/// ws::WsResponseBuilder::new(MyWs, &req, stream)
/// .codec(actix_http::ws::Codec::new())
/// // This will overwrite the codec's max frame-size
/// .frame_size(MAX_FRAME_SIZE) /// .frame_size(MAX_FRAME_SIZE)
/// .protocols(&["A", "B"])
/// .start() /// .start()
/// }
/// #
/// # #[actix_web::main]
/// # async fn main() -> std::io::Result<()> {
/// # HttpServer::new(|| {
/// # App::new()
/// # .service(websocket)
/// # .service(custom_websocket)
/// # })
/// # .bind(("127.0.0.1", 8080))?
/// # .run()
/// # .await
/// # }
/// ``` /// ```
pub struct WsResponseBuilder<'a, A, T> pub struct WsResponseBuilder<'a, A, T>
where where

View File

@@ -1,6 +1,7 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2022-xx-xx
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
## 4.0.1 - 2022-06-11 ## 4.0.1 - 2022-06-11

View File

@@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen)
[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.1)](https://docs.rs/actix-web-codegen/4.0.1) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.0.1)](https://docs.rs/actix-web-codegen/4.0.1)
![Version](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg)
![License](https://img.shields.io/crates/l/actix-web-codegen.svg) ![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.1/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.1) [![dependency status](https://deps.rs/crate/actix-web-codegen/4.0.1/status.svg)](https://deps.rs/crate/actix-web-codegen/4.0.1)

View File

@@ -1,4 +1,4 @@
#[rustversion::stable(1.56)] // MSRV #[rustversion::stable(1.57)] // MSRV
#[test] #[test]
fn compile_macros() { fn compile_macros() {
let t = trybuild::TestCases::new(); let t = trybuild::TestCases::new();

View File

@@ -1,11 +1,13 @@
error: HTTP method defined more than once: `GET` error: HTTP method defined more than once: `GET`
--> $DIR/route-duplicate-method-fail.rs:3:35 --> tests/trybuild/route-duplicate-method-fail.rs:3:35
| |
3 | #[route("/", method="GET", method="GET")] 3 | #[route("/", method="GET", method="GET")]
| ^^^^^ | ^^^^^
error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied
--> $DIR/route-duplicate-method-fail.rs:12:55 --> tests/trybuild/route-duplicate-method-fail.rs:12:55
| |
12 | let srv = actix_test::start(|| App::new().service(index)); 12 | let srv = actix_test::start(|| App::new().service(index));
| ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}`
| |
| required by a bound introduced by this call

View File

@@ -10,4 +10,6 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpSer
--> tests/trybuild/route-missing-method-fail.rs:12:55 --> tests/trybuild/route-missing-method-fail.rs:12:55
| |
12 | let srv = actix_test::start(|| App::new().service(index)); 12 | let srv = actix_test::start(|| App::new().service(index));
| ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}`
| |
| required by a bound introduced by this call

View File

@@ -1,11 +1,13 @@
error: Unexpected HTTP method: `UNEXPECTED` error: Unexpected HTTP method: `UNEXPECTED`
--> $DIR/route-unexpected-method-fail.rs:3:21 --> tests/trybuild/route-unexpected-method-fail.rs:3:21
| |
3 | #[route("/", method="UNEXPECTED")] 3 | #[route("/", method="UNEXPECTED")]
| ^^^^^^^^^^^^ | ^^^^^^^^^^^^
error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied
--> $DIR/route-unexpected-method-fail.rs:12:55 --> tests/trybuild/route-unexpected-method-fail.rs:12:55
| |
12 | let srv = actix_test::start(|| App::new().service(index)); 12 | let srv = actix_test::start(|| App::new().service(index));
| ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}` | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}`
| |
| required by a bound introduced by this call

View File

@@ -1,6 +1,15 @@
# Changelog # Changelog
## Unreleased - 2021-xx-xx ## Unreleased - 2022-xx-xx
### Added
- Add `ServiceRequest::{parts, request}()` getter methods. [#2786]
- Add configuration options for TLS handshake timeout via `HttpServer::{rustls, openssl}_with_config` methods. [#2752]
### Changed
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
[#2752]: https://github.com/actix/actix-web/pull/2752
[#2786]: https://github.com/actix/actix-web/pull/2786
## 4.1.0 - 2022-06-11 ## 4.1.0 - 2022-06-11

View File

@@ -105,14 +105,14 @@ actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] }
awc = { version = "3", features = ["openssl"] } awc = { version = "3", features = ["openssl"] }
brotli = "3.3.3" brotli = "3.3.3"
const-str = "0.3" const-str = "0.4"
criterion = { version = "0.3", features = ["html_reports"] } criterion = { version = "0.3", features = ["html_reports"] }
env_logger = "0.9" env_logger = "0.9"
flate2 = "1.0.13" flate2 = "1.0.13"
futures-util = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = ["std"] }
rand = "0.8" rand = "0.8"
rcgen = "0.8" rcgen = "0.9"
rustls-pemfile = "0.2" rustls-pemfile = "1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
static_assertions = "1" static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.9" } tls-openssl = { package = "openssl", version = "0.10.9" }

View File

@@ -7,7 +7,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.1.0)](https://docs.rs/actix-web/4.1.0) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.1.0)](https://docs.rs/actix-web/4.1.0)
![MSRV](https://img.shields.io/badge/rustc-1.56+-ab6000.svg) ![MSRV](https://img.shields.io/badge/rustc-1.57+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/4.1.0/status.svg)](https://deps.rs/crate/actix-web/4.1.0) [![Dependency Status](https://deps.rs/crate/actix-web/4.1.0/status.svg)](https://deps.rs/crate/actix-web/4.1.0)
<br /> <br />
@@ -33,7 +33,7 @@
- SSL support using OpenSSL or Rustls - SSL support using OpenSSL or Rustls
- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) - Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
- Integrates with the [`awc` HTTP client](https://docs.rs/awc/) - Integrates with the [`awc` HTTP client](https://docs.rs/awc/)
- Runs on stable Rust 1.56+ - Runs on stable Rust 1.57+
## Documentation ## Documentation
@@ -79,6 +79,7 @@ async fn main() -> std::io::Result<()> {
- [Application State](https://github.com/actix/examples/tree/master/basics/state) - [Application State](https://github.com/actix/examples/tree/master/basics/state)
- [JSON Handling](https://github.com/actix/examples/tree/master/json/json) - [JSON Handling](https://github.com/actix/examples/tree/master/json/json)
- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart) - [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart)
- [MongoDB Integration](https://github.com/actix/examples/tree/master/databases/mongodb)
- [Diesel Integration](https://github.com/actix/examples/tree/master/databases/diesel) - [Diesel Integration](https://github.com/actix/examples/tree/master/databases/diesel)
- [SQLite Integration](https://github.com/actix/examples/tree/master/databases/sqlite) - [SQLite Integration](https://github.com/actix/examples/tree/master/databases/sqlite)
- [Postgres Integration](https://github.com/actix/examples/tree/master/databases/postgres) - [Postgres Integration](https://github.com/actix/examples/tree/master/databases/postgres)

View File

@@ -60,7 +60,7 @@ where
/// [`HttpRequest::app_data`](crate::HttpRequest::app_data) method at runtime. /// [`HttpRequest::app_data`](crate::HttpRequest::app_data) method at runtime.
/// ///
/// # [`Data<T>`] /// # [`Data<T>`]
/// Any [`Data<T>`] type added here can utilize it's extractor implementation in handlers. /// Any [`Data<T>`] type added here can utilize its extractor implementation in handlers.
/// Types not wrapped in `Data<T>` cannot use this extractor. See [its docs](Data<T>) for more /// Types not wrapped in `Data<T>` cannot use this extractor. See [its docs](Data<T>) for more
/// about its usage and patterns. /// about its usage and patterns.
/// ///

View File

@@ -257,7 +257,7 @@ impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>; type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
// construct all services factory future with it's resource def and guards. // construct all services factory future with its resource def and guards.
let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| { let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| {
let path = path.clone(); let path = path.clone();
let guards = guards.borrow_mut().take().unwrap_or_default(); let guards = guards.borrow_mut().take().unwrap_or_default();

View File

@@ -153,6 +153,16 @@ impl AppConfig {
} }
impl Default for AppConfig { impl Default for AppConfig {
/// Returns the default AppConfig.
/// Note: The included socket address is "127.0.0.1".
///
/// 127.0.0.1: non-routable meta address that denotes an unknown, invalid or non-applicable target.
/// If you need a service only accessed by itself, use a loopback address.
/// A loopback address for IPv4 is any loopback address that begins with "127".
/// Loopback addresses should be only used to test your application locally.
/// The default configuration provides a loopback address.
///
/// 0.0.0.0: if configured to use this special address, the application will listen to any IP address configured on the machine.
fn default() -> Self { fn default() -> Self {
AppConfig::new( AppConfig::new(
false, false,

View File

@@ -254,7 +254,7 @@ impl Guard for AllGuard {
} }
} }
/// Wraps a guard and inverts the outcome of it's `Guard` implementation. /// Wraps a guard and inverts the outcome of its `Guard` implementation.
/// ///
/// # Examples /// # Examples
/// The handler below will be called for any request method apart from `GET`. /// The handler below will be called for any request method apart from `GET`.
@@ -459,7 +459,7 @@ impl Guard for HostGuard {
return scheme == req_host_uri_scheme; return scheme == req_host_uri_scheme;
} }
// TODO: is the the correct behavior? // TODO: is this the correct behavior?
// falls through if scheme cannot be determined // falls through if scheme cannot be determined
} }

View File

@@ -37,7 +37,7 @@ use crate::{
/// Thanks to Rust's type system, Actix Web can infer the function parameter types. During the /// Thanks to Rust's type system, Actix Web can infer the function parameter types. During the
/// extraction step, the parameter types are described as a tuple type, [`from_request`] is run on /// extraction step, the parameter types are described as a tuple type, [`from_request`] is run on
/// that tuple, and the `Handler::call` implementation for that particular function arity /// that tuple, and the `Handler::call` implementation for that particular function arity
/// destructures the tuple into it's component types and calls your handler function with them. /// destructures the tuple into its component types and calls your handler function with them.
/// ///
/// In pseudo-code the process looks something like this: /// In pseudo-code the process looks something like this:
/// ```ignore /// ```ignore

View File

@@ -251,6 +251,8 @@ static SUPPORTED_ENCODINGS: Lazy<Vec<Encoding>> = Lazy::new(|| {
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::HashSet;
use super::*; use super::*;
use crate::{middleware::DefaultHeaders, test, web, App}; use crate::{middleware::DefaultHeaders, test, web, App};
@@ -305,4 +307,27 @@ mod tests {
let bytes = test::read_body(res).await; let bytes = test::read_body(res).await;
assert_eq!(gzip_decode(bytes), DATA.as_bytes()); assert_eq!(gzip_decode(bytes), DATA.as_bytes());
} }
#[actix_rt::test]
async fn retains_previously_set_vary_header() {
let app = test::init_service({
App::new()
.wrap(Compress::default())
.default_service(web::to(move || {
HttpResponse::Ok()
.insert_header((header::VARY, "x-test"))
.finish()
}))
})
.await;
let req = test::TestRequest::default()
.insert_header((header::ACCEPT_ENCODING, "gzip"))
.to_request();
let res = test::call_service(&app, req).await;
assert_eq!(res.status(), StatusCode::OK);
let vary_headers = res.headers().get_all(header::VARY).collect::<HashSet<_>>();
assert!(vary_headers.contains(&HeaderValue::from_static("x-test")));
assert!(vary_headers.contains(&HeaderValue::from_static("accept-encoding")));
}
} }

View File

@@ -343,7 +343,7 @@ mod response_fut_impl {
// Future is only implemented for BoxBody payload type because it's the most useful for making // Future is only implemented for BoxBody payload type because it's the most useful for making
// simple handlers without async blocks. Making it generic over all MessageBody types requires a // simple handlers without async blocks. Making it generic over all MessageBody types requires a
// future impl on Response which would cause it's body field to be, undesirably, Option<B>. // future impl on Response which would cause its body field to be, undesirably, Option<B>.
// //
// This impl is not particularly efficient due to the Response construction and should probably // This impl is not particularly efficient due to the Response construction and should probably
// not be invoked if performance is important. Prefer an async fn/block in such cases. // not be invoked if performance is important. Prefer an async fn/block in such cases.

View File

@@ -18,6 +18,9 @@ use actix_tls::accept::openssl::reexports::{AlpnError, SslAcceptor, SslAcceptorB
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
use actix_tls::accept::rustls::reexports::ServerConfig as RustlsServerConfig; use actix_tls::accept::rustls::reexports::ServerConfig as RustlsServerConfig;
#[cfg(any(feature = "openssl", feature = "rustls"))]
use actix_http::TlsAcceptorConfig;
use crate::{config::AppConfig, Error}; use crate::{config::AppConfig, Error};
struct Socket { struct Socket {
@@ -30,6 +33,8 @@ struct Config {
keep_alive: KeepAlive, keep_alive: KeepAlive,
client_request_timeout: Duration, client_request_timeout: Duration,
client_disconnect_timeout: Duration, client_disconnect_timeout: Duration,
#[cfg(any(feature = "openssl", feature = "rustls"))]
tls_handshake_timeout: Option<Duration>,
} }
/// An HTTP Server. /// An HTTP Server.
@@ -92,6 +97,8 @@ where
keep_alive: KeepAlive::default(), keep_alive: KeepAlive::default(),
client_request_timeout: Duration::from_secs(5), client_request_timeout: Duration::from_secs(5),
client_disconnect_timeout: Duration::from_secs(1), client_disconnect_timeout: Duration::from_secs(1),
#[cfg(any(feature = "rustls", feature = "openssl"))]
tls_handshake_timeout: None,
})), })),
backlog: 1024, backlog: 1024,
sockets: Vec::new(), sockets: Vec::new(),
@@ -225,6 +232,24 @@ where
self self
} }
/// Set TLS handshake timeout.
///
/// Defines a timeout for TLS handshake. If the TLS handshake does not complete
/// within this time, the connection is closed.
///
/// By default handshake timeout is set to 3000 milliseconds.
#[cfg(any(feature = "openssl", feature = "rustls"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "rustls"))))]
pub fn tls_handshake_timeout(self, dur: Duration) -> Self {
self.config
.lock()
.unwrap()
.tls_handshake_timeout
.replace(dur);
self
}
#[doc(hidden)] #[doc(hidden)]
#[deprecated(since = "4.0.0", note = "Renamed to `client_disconnect_timeout`.")] #[deprecated(since = "4.0.0", note = "Renamed to `client_disconnect_timeout`.")]
pub fn client_shutdown(self, dur: u64) -> Self { pub fn client_shutdown(self, dur: u64) -> Self {
@@ -376,10 +401,15 @@ where
.into_factory() .into_factory()
.map_err(|err| err.into().error_response()); .map_err(|err| err.into().error_response());
let acceptor_config = match c.tls_handshake_timeout {
Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur),
None => TlsAcceptorConfig::default(),
};
svc.finish(map_config(fac, move |_| { svc.finish(map_config(fac, move |_| {
AppConfig::new(true, host.clone(), addr) AppConfig::new(true, host.clone(), addr)
})) }))
.openssl(acceptor.clone()) .openssl_with_config(acceptor.clone(), acceptor_config)
})?; })?;
Ok(self) Ok(self)
@@ -434,10 +464,15 @@ where
.into_factory() .into_factory()
.map_err(|err| err.into().error_response()); .map_err(|err| err.into().error_response());
let acceptor_config = match c.tls_handshake_timeout {
Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur),
None => TlsAcceptorConfig::default(),
};
svc.finish(map_config(fac, move |_| { svc.finish(map_config(fac, move |_| {
AppConfig::new(true, host.clone(), addr) AppConfig::new(true, host.clone(), addr)
})) }))
.rustls(config.clone()) .rustls_with_config(config.clone(), acceptor_config)
})?; })?;
Ok(self) Ok(self)

View File

@@ -95,6 +95,18 @@ impl ServiceRequest {
(&mut self.req, &mut self.payload) (&mut self.req, &mut self.payload)
} }
/// Returns immutable accessors to inner parts.
#[inline]
pub fn parts(&self) -> (&HttpRequest, &Payload) {
(&self.req, &self.payload)
}
/// Returns immutable accessor to inner [`HttpRequest`].
#[inline]
pub fn request(&self) -> &HttpRequest {
&self.req
}
/// Derives a type from this request using an [extractor](crate::FromRequest). /// Derives a type from this request using an [extractor](crate::FromRequest).
/// ///
/// Returns the `T` extractor's `Future` type which can be `await`ed. This is particularly handy /// Returns the `T` extractor's `Future` type which can be `await`ed. This is particularly handy

View File

@@ -183,6 +183,7 @@ mod tests {
assert!(Path::<MyStruct>::from_request(&req, &mut pl).await.is_err()); assert!(Path::<MyStruct>::from_request(&req, &mut pl).await.is_err());
} }
#[allow(clippy::let_unit_value)]
#[actix_rt::test] #[actix_rt::test]
async fn test_tuple_extract() { async fn test_tuple_extract() {
let resource = ResourceDef::new("/{key}/{value}/"); let resource = ResourceDef::new("/{key}/{value}/");

View File

@@ -113,7 +113,7 @@ pub struct BytesExtractFut {
body_fut: HttpMessageBody, body_fut: HttpMessageBody,
} }
impl<'a> Future for BytesExtractFut { impl Future for BytesExtractFut {
type Output = Result<Bytes, Error>; type Output = Result<Bytes, Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
@@ -167,7 +167,7 @@ pub struct StringExtractFut {
encoding: &'static Encoding, encoding: &'static Encoding,
} }
impl<'a> Future for StringExtractFut { impl Future for StringExtractFut {
type Output = Result<String, Error>; type Output = Result<String, Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {

View File

@@ -1,8 +1,8 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2022-xx-xx
### Changed ### Changed
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency. - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
## 3.0.0 - 2022-03-07 ## 3.0.0 - 2022-03-07

View File

@@ -90,7 +90,7 @@ cookie = { version = "0.16", features = ["percent-encode"], optional = true }
tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] } tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features = ["dangerous_configuration"] }
trust-dns-resolver = { version = "0.20.0", optional = true } trust-dns-resolver = { version = "0.21", optional = true }
[dev-dependencies] [dev-dependencies]
actix-http = { version = "3", features = ["openssl"] } actix-http = { version = "3", features = ["openssl"] }
@@ -102,13 +102,13 @@ actix-utils = "3"
actix-web = { version = "4", features = ["openssl"] } actix-web = { version = "4", features = ["openssl"] }
brotli = "3.3.3" brotli = "3.3.3"
const-str = "0.3" const-str = "0.4"
env_logger = "0.9" env_logger = "0.9"
flate2 = "1.0.13" flate2 = "1.0.13"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
static_assertions = "1.1" static_assertions = "1.1"
rcgen = "0.8" rcgen = "0.9"
rustls-pemfile = "0.2" rustls-pemfile = "1"
tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] }
zstd = "0.11" zstd = "0.11"

View File

@@ -1 +1 @@
msrv = "1.56" msrv = "1.57"