mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into advanced_route_macro
This commit is contained in:
commit
170620f287
|
@ -16,7 +16,7 @@ jobs:
|
|||
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
|
||||
- { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc }
|
||||
version:
|
||||
- 1.54.0 # MSRV
|
||||
- 1.57.0 # MSRV
|
||||
- stable
|
||||
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version }}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
# 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
|
||||
- Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021]
|
||||
- Update `tokio-uring` dependency to `0.3`.
|
||||
- Audio files now use `Content-Disposition: inline` instead of `attachment`. [#2645]
|
||||
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||
|
||||
[#2021]: https://github.com/actix/actix-web/pull/2021
|
||||
[#2645]: https://github.com/actix/actix-web/pull/2645
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-files"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
|
@ -47,5 +47,5 @@ actix-server = { version = "2.1", optional = true } # ensure matching tokio-urin
|
|||
[dev-dependencies]
|
||||
actix-rt = "2.7"
|
||||
actix-test = "0.1.0-beta.13"
|
||||
actix-web = "4.0.0"
|
||||
actix-web = "4"
|
||||
tempfile = "3.2"
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
> Static file serving for Actix Web
|
||||
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://docs.rs/actix-files/0.6.0)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||
[](https://docs.rs/actix-files/0.6.1)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-files/0.6.0)
|
||||
[](https://deps.rs/crate/actix-files/0.6.1)
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased - 2022-xx-xx
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
|
||||
|
||||
## 3.0.0-beta.13 - 2022-02-16
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
[](https://docs.rs/actix-http-test/3.0.0-beta.13)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||

|
||||

|
||||
<br>
|
||||
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.13)
|
||||
|
|
|
@ -1,6 +1,25 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased - 2022-xx-xx
|
||||
### Fixed
|
||||
- Websocket parser no longer throws endless overflow errors after receiving an oversized frame. [#2790]
|
||||
|
||||
### Changed
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
|
||||
[#2790]: https://github.com/actix/actix-web/pull/2790
|
||||
|
||||
|
||||
## 3.1.0 - 2022-06-11
|
||||
### Changed
|
||||
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||
|
||||
### Fixed
|
||||
- Revert broken fix in [#2624] that caused erroneous 500 error responses. Temporarily re-introduces [#2357] bug. [#2779]
|
||||
|
||||
[#2357]: https://github.com/actix/actix-web/issues/2357
|
||||
[#2624]: https://github.com/actix/actix-web/issues/2624
|
||||
[#2779]: https://github.com/actix/actix-web/issues/2779
|
||||
|
||||
|
||||
## 3.0.4 - 2022-03-09
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-http"
|
||||
version = "3.0.4"
|
||||
version = "3.1.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
|
@ -37,7 +37,7 @@ ws = [
|
|||
"local-channel",
|
||||
"base64",
|
||||
"rand",
|
||||
"sha-1",
|
||||
"sha1",
|
||||
]
|
||||
|
||||
# TLS via OpenSSL
|
||||
|
@ -86,7 +86,7 @@ h2 = { version = "0.3.9", optional = true }
|
|||
local-channel = { version = "0.1", optional = true }
|
||||
base64 = { version = "0.13", optional = true }
|
||||
rand = { version = "0.8", optional = true }
|
||||
sha-1 = { version = "0.10", optional = true }
|
||||
sha1 = { version = "0.10", optional = true }
|
||||
|
||||
# openssl/rustls
|
||||
actix-tls = { version = "3", default-features = false, optional = true }
|
||||
|
@ -100,7 +100,7 @@ zstd = { version = "0.11", optional = true }
|
|||
actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] }
|
||||
actix-server = "2"
|
||||
actix-tls = { version = "3", features = ["openssl"] }
|
||||
actix-web = "4.0.0"
|
||||
actix-web = "4"
|
||||
|
||||
async-stream = "0.3"
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
> HTTP primitives for the Actix ecosystem.
|
||||
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://docs.rs/actix-http/3.0.4)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||
[](https://docs.rs/actix-http/3.1.0)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-http/3.0.4)
|
||||
[](https://deps.rs/crate/actix-http/3.1.0)
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
|
|
@ -15,14 +15,14 @@ use bitflags::bitflags;
|
|||
use bytes::{Buf, BytesMut};
|
||||
use futures_core::ready;
|
||||
use pin_project_lite::pin_project;
|
||||
use tracing::{debug, error, trace};
|
||||
use tracing::{error, trace};
|
||||
|
||||
use crate::{
|
||||
body::{BodySize, BoxBody, MessageBody},
|
||||
config::ServiceConfig,
|
||||
error::{DispatchError, ParseError, PayloadError},
|
||||
service::HttpFlow,
|
||||
ConnectionType, Error, Extensions, OnConnectData, Request, Response, StatusCode,
|
||||
Error, Extensions, OnConnectData, Request, Response, StatusCode,
|
||||
};
|
||||
|
||||
use super::{
|
||||
|
@ -691,74 +691,12 @@ where
|
|||
let can_not_read = !self.can_read(cx);
|
||||
|
||||
// limit amount of non-processed requests
|
||||
if pipeline_queue_full {
|
||||
if pipeline_queue_full || can_not_read {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut this = self.as_mut().project();
|
||||
|
||||
if can_not_read {
|
||||
debug!("cannot read request payload");
|
||||
|
||||
if let Some(sender) = &this.payload {
|
||||
// ...maybe handler does not want to read any more payload...
|
||||
if let PayloadStatus::Dropped = sender.need_read(cx) {
|
||||
debug!("handler dropped payload early; attempt to clean connection");
|
||||
// ...in which case poll request payload a few times
|
||||
loop {
|
||||
match this.codec.decode(this.read_buf)? {
|
||||
Some(msg) => {
|
||||
match msg {
|
||||
// payload decoded did not yield EOF yet
|
||||
Message::Chunk(Some(_)) => {
|
||||
// if non-clean connection, next loop iter will detect empty
|
||||
// read buffer and close connection
|
||||
}
|
||||
|
||||
// connection is in clean state for next request
|
||||
Message::Chunk(None) => {
|
||||
debug!("connection successfully cleaned");
|
||||
|
||||
// reset dispatcher state
|
||||
let _ = this.payload.take();
|
||||
this.state.set(State::None);
|
||||
|
||||
// break out of payload decode loop
|
||||
break;
|
||||
}
|
||||
|
||||
// Either whole payload is read and loop is broken or more data
|
||||
// was expected in which case connection is closed. In both
|
||||
// situations dispatcher cannot get here.
|
||||
Message::Item(_) => {
|
||||
unreachable!("dispatcher is in payload receive state")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// not enough info to decide if connection is going to be clean or not
|
||||
None => {
|
||||
error!(
|
||||
"handler did not read whole payload and dispatcher could not \
|
||||
drain read buf; return 500 and close connection"
|
||||
);
|
||||
|
||||
this.flags.insert(Flags::SHUTDOWN);
|
||||
let mut res = Response::internal_server_error().drop_body();
|
||||
res.head_mut().set_connection_type(ConnectionType::Close);
|
||||
this.messages.push_back(DispatcherMessage::Error(res));
|
||||
*this.error = Some(DispatchError::HandlerDroppedPayload);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// can_not_read and no request payload
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
let mut updated = false;
|
||||
|
||||
// decode from read buf as many full requests as possible
|
||||
|
@ -904,10 +842,7 @@ where
|
|||
if timer.as_mut().poll(cx).is_ready() {
|
||||
// timeout on first request (slow request) return 408
|
||||
|
||||
trace!(
|
||||
"timed out on slow request; \
|
||||
replying with 408 and closing connection"
|
||||
);
|
||||
trace!("timed out on slow request; replying with 408 and closing connection");
|
||||
|
||||
let _ = self.as_mut().send_error_response(
|
||||
Response::with_body(StatusCode::REQUEST_TIMEOUT, ()),
|
||||
|
|
|
@ -783,6 +783,9 @@ async fn upgrade_handling() {
|
|||
.await;
|
||||
}
|
||||
|
||||
// fix in #2624 reverted temporarily
|
||||
// complete fix tracked in #2745
|
||||
#[ignore]
|
||||
#[actix_rt::test]
|
||||
async fn handler_drop_payload() {
|
||||
let _ = env_logger::try_init();
|
||||
|
|
|
@ -17,7 +17,6 @@ impl Parser {
|
|||
fn parse_metadata(
|
||||
src: &[u8],
|
||||
server: bool,
|
||||
max_size: usize,
|
||||
) -> Result<Option<(usize, bool, OpCode, usize, Option<[u8; 4]>)>, ProtocolError> {
|
||||
let chunk_len = src.len();
|
||||
|
||||
|
@ -60,20 +59,12 @@ impl Parser {
|
|||
return Ok(None);
|
||||
}
|
||||
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;
|
||||
len as usize
|
||||
} else {
|
||||
len as usize
|
||||
};
|
||||
|
||||
// check for max allowed size
|
||||
if length > max_size {
|
||||
return Err(ProtocolError::Overflow);
|
||||
}
|
||||
|
||||
let mask = if server {
|
||||
if chunk_len < idx + 4 {
|
||||
return Ok(None);
|
||||
|
@ -98,11 +89,10 @@ impl Parser {
|
|||
max_size: usize,
|
||||
) -> Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError> {
|
||||
// try to parse ws frame metadata
|
||||
let (idx, finished, opcode, length, mask) =
|
||||
match Parser::parse_metadata(src, server, max_size)? {
|
||||
None => return Ok(None),
|
||||
Some(res) => res,
|
||||
};
|
||||
let (idx, finished, opcode, length, mask) = match Parser::parse_metadata(src, server)? {
|
||||
None => return Ok(None),
|
||||
Some(res) => res,
|
||||
};
|
||||
|
||||
// not enough data
|
||||
if src.len() < idx + length {
|
||||
|
@ -112,6 +102,13 @@ impl Parser {
|
|||
// remove prefix
|
||||
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
|
||||
if length == 0 {
|
||||
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]
|
||||
fn test_ping_frame() {
|
||||
let mut buf = BytesMut::new();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased - 2022-xx-xx
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
|
||||
|
||||
## 0.4.0 - 2022-02-25
|
||||
|
|
|
@ -15,7 +15,7 @@ path = "src/lib.rs"
|
|||
|
||||
[dependencies]
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4.0.0", default-features = false }
|
||||
actix-web = { version = "4", default-features = false }
|
||||
|
||||
bytes = "1"
|
||||
derive_more = "0.99.5"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://docs.rs/actix-multipart/0.4.0)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-multipart/0.4.0)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased - 2022-xx-xx
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
|
||||
|
||||
## 0.5.0 - 2022-02-22
|
||||
|
|
|
@ -21,7 +21,6 @@ default = ["http"]
|
|||
|
||||
[dependencies]
|
||||
bytestring = ">=0.1.5, <2"
|
||||
firestorm = "0.5"
|
||||
http = { version = "0.2.3", optional = true }
|
||||
regex = "1.5"
|
||||
serde = "1"
|
||||
|
@ -29,7 +28,6 @@ tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
|||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
firestorm = { version = "0.5", features = ["enable_system_time"] }
|
||||
http = "0.2.5"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
percent-encoding = "2.1"
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
macro_rules! register {
|
||||
(brackets) => {{
|
||||
register!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
|
||||
}};
|
||||
(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
|
||||
let arr = [
|
||||
concat!("/authorizations"),
|
||||
concat!("/authorizations/", $p1),
|
||||
concat!("/applications/", $p1, "/tokens/", $p2),
|
||||
concat!("/events"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/events"),
|
||||
concat!("/networks/", $p1, "/", $p2, "/events"),
|
||||
concat!("/orgs/", $p1, "/events"),
|
||||
concat!("/users/", $p1, "/received_events"),
|
||||
concat!("/users/", $p1, "/received_events/public"),
|
||||
concat!("/users/", $p1, "/events"),
|
||||
concat!("/users/", $p1, "/events/public"),
|
||||
concat!("/users/", $p1, "/events/orgs/", $p2),
|
||||
concat!("/feeds"),
|
||||
concat!("/notifications"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/notifications"),
|
||||
concat!("/notifications/threads/", $p1),
|
||||
concat!("/notifications/threads/", $p1, "/subscription"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/stargazers"),
|
||||
concat!("/users/", $p1, "/starred"),
|
||||
concat!("/user/starred"),
|
||||
concat!("/user/starred/", $p1, "/", $p2),
|
||||
concat!("/repos/", $p1, "/", $p2, "/subscribers"),
|
||||
concat!("/users/", $p1, "/subscriptions"),
|
||||
concat!("/user/subscriptions"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/subscription"),
|
||||
concat!("/user/subscriptions/", $p1, "/", $p2),
|
||||
concat!("/users/", $p1, "/gists"),
|
||||
concat!("/gists"),
|
||||
concat!("/gists/", $p1),
|
||||
concat!("/gists/", $p1, "/star"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/git/refs"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3),
|
||||
concat!("/issues"),
|
||||
concat!("/user/issues"),
|
||||
concat!("/orgs/", $p1, "/issues"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/issues"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/assignees"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/labels"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/labels/", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/milestones/"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3),
|
||||
concat!("/emojis"),
|
||||
concat!("/gitignore/templates"),
|
||||
concat!("/gitignore/templates/", $p1),
|
||||
concat!("/meta"),
|
||||
concat!("/rate_limit"),
|
||||
concat!("/users/", $p1, "/orgs"),
|
||||
concat!("/user/orgs"),
|
||||
concat!("/orgs/", $p1),
|
||||
concat!("/orgs/", $p1, "/members"),
|
||||
concat!("/orgs/", $p1, "/members", $p2),
|
||||
concat!("/orgs/", $p1, "/public_members"),
|
||||
concat!("/orgs/", $p1, "/public_members/", $p2),
|
||||
concat!("/orgs/", $p1, "/teams"),
|
||||
concat!("/teams/", $p1),
|
||||
concat!("/teams/", $p1, "/members"),
|
||||
concat!("/teams/", $p1, "/members", $p2),
|
||||
concat!("/teams/", $p1, "/repos"),
|
||||
concat!("/teams/", $p1, "/repos/", $p2, "/", $p3),
|
||||
concat!("/user/teams"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/pulls"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"),
|
||||
concat!("/user/repos"),
|
||||
concat!("/users/", $p1, "/repos"),
|
||||
concat!("/orgs/", $p1, "/repos"),
|
||||
concat!("/repositories"),
|
||||
concat!("/repos/", $p1, "/", $p2),
|
||||
concat!("/repos/", $p1, "/", $p2, "/contributors"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/languages"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/teams"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/tags"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/branches"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/branches/", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/collaborators"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/comments"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/commits"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/readme"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/keys"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/keys", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/downloads"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/downloads", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/forks"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/hooks"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/hooks", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/releases"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3),
|
||||
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/stats/contributors"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/stats/participation"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"),
|
||||
concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3),
|
||||
concat!("/search/repositories"),
|
||||
concat!("/search/code"),
|
||||
concat!("/search/issues"),
|
||||
concat!("/search/users"),
|
||||
concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4),
|
||||
concat!("/legacy/repos/search/", $p1),
|
||||
concat!("/legacy/user/search/", $p1),
|
||||
concat!("/legacy/user/email/", $p1),
|
||||
concat!("/users/", $p1),
|
||||
concat!("/user"),
|
||||
concat!("/users"),
|
||||
concat!("/user/emails"),
|
||||
concat!("/users/", $p1, "/followers"),
|
||||
concat!("/user/followers"),
|
||||
concat!("/users/", $p1, "/following"),
|
||||
concat!("/user/following"),
|
||||
concat!("/user/following/", $p1),
|
||||
concat!("/users/", $p1, "/following", $p2),
|
||||
concat!("/users/", $p1, "/keys"),
|
||||
concat!("/user/keys"),
|
||||
concat!("/user/keys/", $p1),
|
||||
];
|
||||
|
||||
arr.to_vec()
|
||||
}};
|
||||
}
|
||||
|
||||
static PATHS: [&str; 5] = [
|
||||
"/authorizations",
|
||||
"/user/repos",
|
||||
"/repos/rust-lang/rust/stargazers",
|
||||
"/orgs/rust-lang/public_members/nikomatsakis",
|
||||
"/repos/rust-lang/rust/releases/1.51.0",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
let mut router = actix_router::Router::<bool>::build();
|
||||
|
||||
for route in register!(brackets) {
|
||||
router.path(route, true);
|
||||
}
|
||||
|
||||
let actix = router.finish();
|
||||
|
||||
if firestorm::enabled() {
|
||||
firestorm::bench("target", || {
|
||||
for &route in &PATHS {
|
||||
let mut path = actix_router::Path::new(route);
|
||||
actix.recognize(&mut path).unwrap();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
use std::borrow::Cow;
|
||||
use std::ops::{DerefMut, Index};
|
||||
|
||||
use firestorm::profile_method;
|
||||
use serde::de;
|
||||
|
||||
use crate::{de::PathDeserializer, Resource, ResourcePath};
|
||||
|
@ -52,7 +51,6 @@ impl<T: ResourcePath> Path<T> {
|
|||
/// Returns full path as a string.
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> &str {
|
||||
profile_method!(as_str);
|
||||
self.path.path()
|
||||
}
|
||||
|
||||
|
@ -61,7 +59,6 @@ impl<T: ResourcePath> Path<T> {
|
|||
/// Returns empty string if no more is to be processed.
|
||||
#[inline]
|
||||
pub fn unprocessed(&self) -> &str {
|
||||
profile_method!(unprocessed);
|
||||
// clamp skip to path length
|
||||
let skip = (self.skip as usize).min(self.as_str().len());
|
||||
&self.path.path()[skip..]
|
||||
|
@ -72,8 +69,6 @@ impl<T: ResourcePath> Path<T> {
|
|||
#[deprecated(since = "0.6.0", note = "Use `.as_str()` or `.unprocessed()`.")]
|
||||
#[inline]
|
||||
pub fn path(&self) -> &str {
|
||||
profile_method!(path);
|
||||
|
||||
let skip = self.skip as usize;
|
||||
let path = self.path.path();
|
||||
if skip <= path.len() {
|
||||
|
@ -86,8 +81,6 @@ impl<T: ResourcePath> Path<T> {
|
|||
/// Set new path.
|
||||
#[inline]
|
||||
pub fn set(&mut self, path: T) {
|
||||
profile_method!(set);
|
||||
|
||||
self.skip = 0;
|
||||
self.path = path;
|
||||
self.segments.clear();
|
||||
|
@ -96,8 +89,6 @@ impl<T: ResourcePath> Path<T> {
|
|||
/// Reset state.
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
profile_method!(reset);
|
||||
|
||||
self.skip = 0;
|
||||
self.segments.clear();
|
||||
}
|
||||
|
@ -105,13 +96,10 @@ impl<T: ResourcePath> Path<T> {
|
|||
/// Skip first `n` chars in path.
|
||||
#[inline]
|
||||
pub fn skip(&mut self, n: u16) {
|
||||
profile_method!(skip);
|
||||
self.skip += n;
|
||||
}
|
||||
|
||||
pub(crate) fn add(&mut self, name: impl Into<Cow<'static, str>>, value: PathItem) {
|
||||
profile_method!(add);
|
||||
|
||||
match value {
|
||||
PathItem::Static(s) => self.segments.push((name.into(), PathItem::Static(s))),
|
||||
PathItem::Segment(begin, end) => self.segments.push((
|
||||
|
@ -127,8 +115,6 @@ impl<T: ResourcePath> Path<T> {
|
|||
name: impl Into<Cow<'static, str>>,
|
||||
value: impl Into<Cow<'static, str>>,
|
||||
) {
|
||||
profile_method!(add_static);
|
||||
|
||||
self.segments
|
||||
.push((name.into(), PathItem::Static(value.into())));
|
||||
}
|
||||
|
@ -147,8 +133,6 @@ impl<T: ResourcePath> Path<T> {
|
|||
|
||||
/// Get matched parameter by name without type conversion
|
||||
pub fn get(&self, name: &str) -> Option<&str> {
|
||||
profile_method!(get);
|
||||
|
||||
for (seg_name, val) in self.segments.iter() {
|
||||
if name == seg_name {
|
||||
return match val {
|
||||
|
@ -167,8 +151,6 @@ impl<T: ResourcePath> Path<T> {
|
|||
///
|
||||
/// If keyed parameter is not available empty string is used as default value.
|
||||
pub fn query(&self, key: &str) -> &str {
|
||||
profile_method!(query);
|
||||
|
||||
if let Some(s) = self.get(key) {
|
||||
s
|
||||
} else {
|
||||
|
@ -186,7 +168,6 @@ impl<T: ResourcePath> Path<T> {
|
|||
|
||||
/// Try to deserialize matching parameters to a specified type `U`
|
||||
pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
|
||||
profile_method!(load);
|
||||
de::Deserialize::deserialize(PathDeserializer::new(self))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ use std::{
|
|||
mem,
|
||||
};
|
||||
|
||||
use firestorm::{profile_fn, profile_method, profile_section};
|
||||
use regex::{escape, Regex, RegexSet};
|
||||
use tracing::error;
|
||||
|
||||
|
@ -272,7 +271,6 @@ impl ResourceDef {
|
|||
/// assert!(!resource.is_match("/foo"));
|
||||
/// ```
|
||||
pub fn new<T: IntoPatterns>(paths: T) -> Self {
|
||||
profile_method!(new);
|
||||
Self::construct(paths, false)
|
||||
}
|
||||
|
||||
|
@ -300,7 +298,6 @@ impl ResourceDef {
|
|||
/// assert!(!resource.is_match("/foo"));
|
||||
/// ```
|
||||
pub fn prefix<T: IntoPatterns>(paths: T) -> Self {
|
||||
profile_method!(prefix);
|
||||
ResourceDef::construct(paths, true)
|
||||
}
|
||||
|
||||
|
@ -325,7 +322,6 @@ impl ResourceDef {
|
|||
/// assert!(!resource.is_match("user/123"));
|
||||
/// ```
|
||||
pub fn root_prefix(path: &str) -> Self {
|
||||
profile_method!(root_prefix);
|
||||
ResourceDef::prefix(insert_slash(path).into_owned())
|
||||
}
|
||||
|
||||
|
@ -549,8 +545,6 @@ impl ResourceDef {
|
|||
/// ```
|
||||
#[inline]
|
||||
pub fn is_match(&self, path: &str) -> bool {
|
||||
profile_method!(is_match);
|
||||
|
||||
// this function could be expressed as:
|
||||
// `self.find_match(path).is_some()`
|
||||
// but this skips some checks and uses potentially faster regex methods
|
||||
|
@ -598,8 +592,6 @@ impl ResourceDef {
|
|||
/// assert_eq!(resource.find_match("/profile/1234"), Some(13));
|
||||
/// ```
|
||||
pub fn find_match(&self, path: &str) -> Option<usize> {
|
||||
profile_method!(find_match);
|
||||
|
||||
match &self.pat_type {
|
||||
PatternType::Static(pattern) => self.static_match(pattern, path),
|
||||
|
||||
|
@ -634,7 +626,6 @@ impl ResourceDef {
|
|||
/// assert_eq!(path.unprocessed(), "");
|
||||
/// ```
|
||||
pub fn capture_match_info<R: Resource>(&self, resource: &mut R) -> bool {
|
||||
profile_method!(capture_match_info);
|
||||
self.capture_match_info_fn(resource, |_| true)
|
||||
}
|
||||
|
||||
|
@ -680,53 +671,35 @@ impl ResourceDef {
|
|||
R: Resource,
|
||||
F: FnOnce(&R) -> bool,
|
||||
{
|
||||
profile_method!(capture_match_info_fn);
|
||||
|
||||
let mut segments = <[PathItem; MAX_DYNAMIC_SEGMENTS]>::default();
|
||||
let path = resource.resource_path();
|
||||
let path_str = path.unprocessed();
|
||||
|
||||
let (matched_len, matched_vars) = match &self.pat_type {
|
||||
PatternType::Static(pattern) => {
|
||||
profile_section!(pattern_static_or_prefix);
|
||||
|
||||
match self.static_match(pattern, path_str) {
|
||||
Some(len) => (len, None),
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
PatternType::Static(pattern) => match self.static_match(pattern, path_str) {
|
||||
Some(len) => (len, None),
|
||||
None => return false,
|
||||
},
|
||||
|
||||
PatternType::Dynamic(re, names) => {
|
||||
profile_section!(pattern_dynamic);
|
||||
|
||||
let captures = {
|
||||
profile_section!(pattern_dynamic_regex_exec);
|
||||
|
||||
match re.captures(path.unprocessed()) {
|
||||
Some(captures) => captures,
|
||||
_ => return false,
|
||||
}
|
||||
let captures = match re.captures(path.unprocessed()) {
|
||||
Some(captures) => captures,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
{
|
||||
profile_section!(pattern_dynamic_extract_captures);
|
||||
|
||||
for (no, name) in names.iter().enumerate() {
|
||||
if let Some(m) = captures.name(name) {
|
||||
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
|
||||
} else {
|
||||
error!("Dynamic path match but not all segments found: {}", name);
|
||||
return false;
|
||||
}
|
||||
for (no, name) in names.iter().enumerate() {
|
||||
if let Some(m) = captures.name(name) {
|
||||
segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16);
|
||||
} else {
|
||||
error!("Dynamic path match but not all segments found: {}", name);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
(captures[1].len(), Some(names))
|
||||
}
|
||||
|
||||
PatternType::DynamicSet(re, params) => {
|
||||
profile_section!(pattern_dynamic_set);
|
||||
|
||||
let path = path.unprocessed();
|
||||
let (pattern, names) = match re.matches(path).into_iter().next() {
|
||||
Some(idx) => ¶ms[idx],
|
||||
|
@ -809,7 +782,6 @@ impl ResourceDef {
|
|||
I: IntoIterator,
|
||||
I::Item: AsRef<str>,
|
||||
{
|
||||
profile_method!(resource_path_from_iter);
|
||||
let mut iter = values.into_iter();
|
||||
self.build_resource_path(path, |_| iter.next())
|
||||
}
|
||||
|
@ -845,7 +817,6 @@ impl ResourceDef {
|
|||
V: AsRef<str>,
|
||||
S: BuildHasher,
|
||||
{
|
||||
profile_method!(resource_path_from_map);
|
||||
self.build_resource_path(path, |name| values.get(name))
|
||||
}
|
||||
|
||||
|
@ -866,8 +837,6 @@ impl ResourceDef {
|
|||
}
|
||||
|
||||
fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
|
||||
profile_method!(construct);
|
||||
|
||||
let patterns = paths.patterns();
|
||||
let (pat_type, segments) = match &patterns {
|
||||
Patterns::Single(pattern) => ResourceDef::parse(pattern, is_prefix, false),
|
||||
|
@ -926,8 +895,6 @@ impl ResourceDef {
|
|||
/// # Panics
|
||||
/// Panics if given patterns does not contain a dynamic segment.
|
||||
fn parse_param(pattern: &str) -> (PatternSegment, String, &str, bool) {
|
||||
profile_method!(parse_param);
|
||||
|
||||
const DEFAULT_PATTERN: &str = "[^/]+";
|
||||
const DEFAULT_PATTERN_TAIL: &str = ".*";
|
||||
|
||||
|
@ -997,8 +964,6 @@ impl ResourceDef {
|
|||
is_prefix: bool,
|
||||
force_dynamic: bool,
|
||||
) -> (PatternType, Vec<PatternSegment>) {
|
||||
profile_method!(parse);
|
||||
|
||||
if !force_dynamic && pattern.find('{').is_none() && !pattern.ends_with('*') {
|
||||
// pattern is static
|
||||
return (
|
||||
|
@ -1131,8 +1096,6 @@ impl From<String> for ResourceDef {
|
|||
}
|
||||
|
||||
pub(crate) fn insert_slash(path: &str) -> Cow<'_, str> {
|
||||
profile_fn!(insert_slash);
|
||||
|
||||
if !path.is_empty() && !path.starts_with('/') {
|
||||
let mut new_path = String::with_capacity(path.len() + 1);
|
||||
new_path.push('/');
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use firestorm::profile_method;
|
||||
|
||||
use crate::{IntoPatterns, Resource, ResourceDef};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
|
@ -30,7 +28,6 @@ impl<T, U> Router<T, U> {
|
|||
where
|
||||
R: Resource,
|
||||
{
|
||||
profile_method!(recognize);
|
||||
self.recognize_fn(resource, |_, _| true)
|
||||
}
|
||||
|
||||
|
@ -39,7 +36,6 @@ impl<T, U> Router<T, U> {
|
|||
where
|
||||
R: Resource,
|
||||
{
|
||||
profile_method!(recognize_mut);
|
||||
self.recognize_mut_fn(resource, |_, _| true)
|
||||
}
|
||||
|
||||
|
@ -55,8 +51,6 @@ impl<T, U> Router<T, U> {
|
|||
R: Resource,
|
||||
F: FnMut(&R, &U) -> bool,
|
||||
{
|
||||
profile_method!(recognize_checked);
|
||||
|
||||
for (rdef, val, ctx) in self.routes.iter() {
|
||||
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
|
||||
return Some((val, ResourceId(rdef.id())));
|
||||
|
@ -77,8 +71,6 @@ impl<T, U> Router<T, U> {
|
|||
R: Resource,
|
||||
F: FnMut(&R, &U) -> bool,
|
||||
{
|
||||
profile_method!(recognize_mut_checked);
|
||||
|
||||
for (rdef, val, ctx) in self.routes.iter_mut() {
|
||||
if rdef.capture_match_info_fn(resource, |res| check(res, ctx)) {
|
||||
return Some((val, ResourceId(rdef.id())));
|
||||
|
@ -104,7 +96,6 @@ impl<T, U> RouterBuilder<T, U> {
|
|||
val: T,
|
||||
ctx: U,
|
||||
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||
profile_method!(push);
|
||||
self.routes.push((rdef, val, ctx));
|
||||
self.routes
|
||||
.last_mut()
|
||||
|
@ -131,7 +122,6 @@ where
|
|||
path: impl IntoPatterns,
|
||||
val: T,
|
||||
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||
profile_method!(path);
|
||||
self.push(ResourceDef::new(path), val, U::default())
|
||||
}
|
||||
|
||||
|
@ -141,13 +131,11 @@ where
|
|||
prefix: impl IntoPatterns,
|
||||
val: T,
|
||||
) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||
profile_method!(prefix);
|
||||
self.push(ResourceDef::prefix(prefix), val, U::default())
|
||||
}
|
||||
|
||||
/// Registers resource for [`ResourceDef`].
|
||||
pub fn rdef(&mut self, rdef: ResourceDef, val: T) -> (&mut ResourceDef, &mut T, &mut U) {
|
||||
profile_method!(rdef);
|
||||
self.push(rdef, val, U::default())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased - 2022-xx-xx
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
|
||||
|
||||
## 0.1.0-beta.13 - 2022-02-16
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased - 2022-xx-xx
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
|
||||
|
||||
## 4.1.0 - 2022-03-02
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
[](https://crates.io/crates/actix-web-actors)
|
||||
[](https://docs.rs/actix-web-actors/4.1.0)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-web-actors/4.1.0)
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2022-xx-xx
|
||||
- Added `#[actix_web::routes]` macro to support multiple routes on one handler
|
||||
- Added `#[routes]` macro to support multiple paths for one handler. [#2718]
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
|
||||
[#2718]: https://github.com/actix/actix-web/pull/2718
|
||||
|
||||
|
||||
## 4.0.1 - 2022-06-11
|
||||
- Fix support for guard paths in route handler macros. [#2771]
|
||||
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||
|
||||
[#2771]: https://github.com/actix/actix-web/pull/2771
|
||||
|
||||
|
||||
## 4.0.0 - 2022-02-24
|
||||
- Version aligned with `actix-web` and will remain in sync going forward.
|
||||
- No significant changes since `0.5.0`.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-web-codegen"
|
||||
version = "4.0.0"
|
||||
version = "4.0.1"
|
||||
description = "Routing and runtime macros for Actix Web"
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
> Routing and runtime macros for Actix Web.
|
||||
|
||||
[](https://crates.io/crates/actix-web-codegen)
|
||||
[](https://docs.rs/actix-web-codegen/4.0.0)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
|
||||
[](https://docs.rs/actix-web-codegen/4.0.1)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-web-codegen/4.0.0)
|
||||
[](https://deps.rs/crate/actix-web-codegen/4.0.1)
|
||||
[](https://crates.io/crates/actix-web-codegen)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ impl TryFrom<&syn::LitStr> for MethodType {
|
|||
struct Args {
|
||||
path: syn::LitStr,
|
||||
resource_name: Option<syn::LitStr>,
|
||||
guards: Vec<Ident>,
|
||||
guards: Vec<Path>,
|
||||
wrappers: Vec<syn::Type>,
|
||||
methods: HashSet<MethodType>,
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ impl Args {
|
|||
}
|
||||
} else if nv.path.is_ident("guard") {
|
||||
if let syn::Lit::Str(lit) = nv.lit {
|
||||
guards.push(Ident::new(&lit.value(), Span::call_site()));
|
||||
guards.push(lit.parse::<Path>()?);
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
nv.lit,
|
||||
|
|
|
@ -106,6 +106,21 @@ async fn custom_resource_name_test<'a>(req: actix_web::HttpRequest) -> impl Resp
|
|||
HttpResponse::Ok()
|
||||
}
|
||||
|
||||
mod guard_module {
|
||||
use actix_web::{guard::GuardContext, http::header};
|
||||
|
||||
pub fn guard(ctx: &GuardContext) -> bool {
|
||||
ctx.header::<header::Accept>()
|
||||
.map(|h| h.preference() == "image/*")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/test/guard", guard = "guard_module::guard")]
|
||||
async fn guard_test() -> impl Responder {
|
||||
HttpResponse::Ok()
|
||||
}
|
||||
|
||||
pub struct ChangeStatusCode;
|
||||
|
||||
impl<S, B> Transform<S, ServiceRequest> for ChangeStatusCode
|
||||
|
@ -198,6 +213,7 @@ async fn test_body() {
|
|||
.service(route_test)
|
||||
.service(routes_test)
|
||||
.service(custom_resource_name_test)
|
||||
.service(guard_test)
|
||||
});
|
||||
let request = srv.request(http::Method::GET, srv.url("/test"));
|
||||
let response = request.send().await.unwrap();
|
||||
|
@ -268,6 +284,12 @@ async fn test_body() {
|
|||
let request = srv.request(http::Method::GET, srv.url("/custom_resource_name"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let request = srv
|
||||
.request(http::Method::GET, srv.url("/test/guard"))
|
||||
.insert_header(("Accept", "image/*"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#[rustversion::stable(1.54)] // MSRV
|
||||
#[rustversion::stable(1.57)] // MSRV
|
||||
#[test]
|
||||
fn compile_macros() {
|
||||
let t = trybuild::TestCases::new();
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
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")]
|
||||
| ^^^^^
|
||||
|
||||
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));
|
||||
| ^^^^^ 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
|
||||
|
|
|
@ -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
|
||||
|
|
||||
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
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
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")]
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
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));
|
||||
| ^^^^^ 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
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased - 2022-xx-xx
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
### Added
|
||||
- Add `ServiceRequest::{parts, request}()` getter methods. [#2786]
|
||||
|
||||
[#2786]: https://github.com/actix/actix-web/pull/2786
|
||||
|
||||
## 4.1.0 - 2022-06-11
|
||||
### Added
|
||||
- Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647]
|
||||
- Add `Route::wrap()` to allow individual routes to use middleware. [#2725]
|
||||
- Add `ServiceConfig::default_service()`. [#2338] [#2743]
|
||||
- Implement `ResponseError` for `std::convert::Infallible`
|
||||
|
||||
### Changed
|
||||
- Minimum supported Rust version (MSRV) is now 1.56 due to transitive `hashbrown` dependency.
|
||||
|
||||
### Fixed
|
||||
- Clear connection-level data on `HttpRequest` drop. [#2742]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-web"
|
||||
version = "4.0.1"
|
||||
version = "4.1.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
<p>
|
||||
|
||||
[](https://crates.io/crates/actix-web)
|
||||
[](https://docs.rs/actix-web/4.0.1)
|
||||

|
||||
[](https://docs.rs/actix-web/4.1.0)
|
||||

|
||||

|
||||
[](https://deps.rs/crate/actix-web/4.0.1)
|
||||
[](https://deps.rs/crate/actix-web/4.1.0)
|
||||
<br />
|
||||
[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/actix/actix-web)
|
||||
|
@ -33,7 +33,7 @@
|
|||
- SSL support using OpenSSL or Rustls
|
||||
- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
|
||||
- Integrates with the [`awc` HTTP client](https://docs.rs/awc/)
|
||||
- Runs on stable Rust 1.54+
|
||||
- Runs on stable Rust 1.57+
|
||||
|
||||
## Documentation
|
||||
|
||||
|
@ -79,6 +79,7 @@ async fn main() -> std::io::Result<()> {
|
|||
- [Application State](https://github.com/actix/examples/tree/master/basics/state)
|
||||
- [JSON Handling](https://github.com/actix/examples/tree/master/json/json)
|
||||
- [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)
|
||||
- [SQLite Integration](https://github.com/actix/examples/tree/master/databases/sqlite)
|
||||
- [Postgres Integration](https://github.com/actix/examples/tree/master/databases/postgres)
|
||||
|
|
|
@ -60,7 +60,7 @@ where
|
|||
/// [`HttpRequest::app_data`](crate::HttpRequest::app_data) method at runtime.
|
||||
///
|
||||
/// # [`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
|
||||
/// about its usage and patterns.
|
||||
///
|
||||
|
|
|
@ -257,7 +257,7 @@ impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
|||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||
|
||||
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 path = path.clone();
|
||||
let guards = guards.borrow_mut().take().unwrap_or_default();
|
||||
|
|
|
@ -153,6 +153,16 @@ impl 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 {
|
||||
AppConfig::new(
|
||||
false,
|
||||
|
|
|
@ -51,12 +51,6 @@ impl StdError for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<std::convert::Infallible> for Error {
|
||||
fn from(val: std::convert::Infallible) -> Self {
|
||||
match val {}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Error` for any error that implements `ResponseError`
|
||||
impl<T: ResponseError + 'static> From<T> for Error {
|
||||
fn from(err: T) -> Error {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! `ResponseError` trait and foreign impls.
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
error::Error as StdError,
|
||||
fmt,
|
||||
io::{self, Write as _},
|
||||
|
@ -54,6 +55,15 @@ downcast_dyn!(ResponseError);
|
|||
|
||||
impl ResponseError for Box<dyn StdError + 'static> {}
|
||||
|
||||
impl ResponseError for Infallible {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {}
|
||||
}
|
||||
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
impl ResponseError for actix_tls::accept::openssl::reexports::Error {}
|
||||
|
||||
|
|
|
@ -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
|
||||
/// 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;
|
||||
}
|
||||
|
||||
// TODO: is the the correct behavior?
|
||||
// TODO: is this the correct behavior?
|
||||
// falls through if scheme cannot be determined
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ use crate::{
|
|||
/// 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
|
||||
/// 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:
|
||||
/// ```ignore
|
||||
|
|
|
@ -343,7 +343,7 @@ mod response_fut_impl {
|
|||
|
||||
// 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
|
||||
// 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
|
||||
// not be invoked if performance is important. Prefer an async fn/block in such cases.
|
||||
|
|
|
@ -95,6 +95,18 @@ impl ServiceRequest {
|
|||
(&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).
|
||||
///
|
||||
/// Returns the `T` extractor's `Future` type which can be `await`ed. This is particularly handy
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
## Unreleased - 2022-xx-xx
|
||||
### Changed
|
||||
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
|
||||
|
||||
|
||||
## 3.0.0 - 2022-03-07
|
||||
|
|
|
@ -1 +1 @@
|
|||
msrv = "1.54"
|
||||
msrv = "1.57"
|
||||
|
|
Loading…
Reference in New Issue