::from_query()` to extract parameters from a query string. #846
* `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors.
@@ -512,7 +528,7 @@
## [1.0.0-beta.4] - 2019-05-12
-### Add
+### Added
* Allow to set/override app data on scope level
@@ -538,7 +554,7 @@
* CORS handling without headers #702
-* Allow to construct `Data` instances to avoid double `Arc` for `Send + Sync` types.
+* Allow constructing `Data` instances to avoid double `Arc` for `Send + Sync` types.
### Fixed
@@ -602,7 +618,7 @@
### Changed
-* Allow to use any service as default service.
+* Allow using any service as default service.
* Remove generic type for request payload, always use default.
@@ -665,13 +681,13 @@
### Added
-* rustls support
+* Rustls support
### Changed
-* use forked cookie
+* Use forked cookie
-* multipart::Field renamed to MultipartField
+* Multipart::Field renamed to MultipartField
## [1.0.0-alpha.1] - 2019-03-28
diff --git a/Cargo.toml b/Cargo.toml
index 1a1b8645c..f3a6271ee 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "actix-web"
-version = "4.0.0-beta.3"
+version = "4.0.0-beta.4"
authors = ["Nikolay Kim "]
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
readme = "README.md"
@@ -66,7 +66,7 @@ required-features = ["compress"]
[[test]]
name = "test_server"
-required-features = ["compress"]
+required-features = ["compress", "cookies"]
[[example]]
name = "on_connect"
@@ -80,15 +80,15 @@ required-features = ["rustls"]
actix-codec = "0.4.0-beta.1"
actix-macros = "0.2.0"
actix-router = "0.2.7"
-actix-rt = "2"
+actix-rt = "2.1"
actix-server = "2.0.0-beta.3"
actix-service = "2.0.0-beta.4"
actix-utils = "3.0.0-beta.2"
-actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true }
+actix-tls = { version = "3.0.0-beta.4", default-features = false, optional = true }
-actix-web-codegen = "0.5.0-beta.1"
-actix-http = "3.0.0-beta.3"
-awc = { version = "3.0.0-beta.2", default-features = false }
+actix-web-codegen = "0.5.0-beta.2"
+actix-http = "3.0.0-beta.4"
+awc = { version = "3.0.0-beta.3", default-features = false }
ahash = "0.7"
bytes = "1"
@@ -105,18 +105,12 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_urlencoded = "0.7"
smallvec = "1.6"
-socket2 = "0.3.16"
+socket2 = "0.4.0"
time = { version = "0.2.23", default-features = false, features = ["std"] }
tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
tls-rustls = { package = "rustls", version = "0.19.0", optional = true }
url = "2.1"
-[target.'cfg(windows)'.dependencies.tls-openssl]
-version = "0.10.9"
-package = "openssl"
-features = ["vendored"]
-optional = true
-
[dev-dependencies]
brotli2 = "0.3.2"
criterion = "0.3"
diff --git a/README.md b/README.md
index bf68e7961..64fd7d08d 100644
--- a/README.md
+++ b/README.md
@@ -6,10 +6,10 @@
[](https://crates.io/crates/actix-web)
-[](https://docs.rs/actix-web/4.0.0-beta.2)
+[](https://docs.rs/actix-web/4.0.0-beta.4)
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)

-[](https://deps.rs/crate/actix-web/4.0.0-beta.2)
+[](https://deps.rs/crate/actix-web/4.0.0-beta.4)
[](https://github.com/actix/actix-web/actions)
[](https://codecov.io/gh/actix/actix-web)
@@ -71,18 +71,18 @@ async fn main() -> std::io::Result<()> {
### More examples
-* [Basic Setup](https://github.com/actix/examples/tree/master/basics/)
-* [Application State](https://github.com/actix/examples/tree/master/state/)
-* [JSON Handling](https://github.com/actix/examples/tree/master/json/)
-* [Multipart Streams](https://github.com/actix/examples/tree/master/multipart/)
-* [Diesel Integration](https://github.com/actix/examples/tree/master/diesel/)
-* [r2d2 Integration](https://github.com/actix/examples/tree/master/r2d2/)
-* [Simple WebSocket](https://github.com/actix/examples/tree/master/websocket/)
-* [Tera Templates](https://github.com/actix/examples/tree/master/template_tera/)
-* [Askama Templates](https://github.com/actix/examples/tree/master/template_askama/)
-* [HTTPS using Rustls](https://github.com/actix/examples/tree/master/rustls/)
-* [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/openssl/)
-* [WebSocket Chat](https://github.com/actix/examples/tree/master/websocket-chat/)
+* [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/)
+* [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/)
+* [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/)
+* [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/)
+* [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/)
+* [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/)
+* [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/)
+* [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/)
+* [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/)
+* [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/)
You may consider checking out
[this directory](https://github.com/actix/examples/tree/master/) for more examples.
diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md
index 6e2a241ac..c035d5afe 100644
--- a/actix-files/CHANGES.md
+++ b/actix-files/CHANGES.md
@@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx
+## 0.6.0-beta.3 - 2021-03-09
+* No notable changes.
+
+
## 0.6.0-beta.2 - 2021-02-10
* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887]
* Replace `v_htmlescape` with `askama_escape`. [#1953]
diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml
index 08b7b36fc..49cd6966c 100644
--- a/actix-files/Cargo.toml
+++ b/actix-files/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "actix-files"
-version = "0.6.0-beta.2"
+version = "0.6.0-beta.3"
authors = ["Nikolay Kim "]
description = "Static file serving for Actix Web"
readme = "README.md"
@@ -17,7 +17,7 @@ name = "actix_files"
path = "src/lib.rs"
[dependencies]
-actix-web = { version = "4.0.0-beta.3", default-features = false }
+actix-web = { version = "4.0.0-beta.4", default-features = false }
actix-service = "2.0.0-beta.4"
askama_escape = "0.10"
@@ -33,5 +33,5 @@ mime_guess = "2.0.1"
percent-encoding = "2.1"
[dev-dependencies]
-actix-rt = "2"
-actix-web = "4.0.0-beta.3"
+actix-rt = "2.1"
+actix-web = "4.0.0-beta.4"
diff --git a/actix-files/README.md b/actix-files/README.md
index 463f20224..c7b7424ec 100644
--- a/actix-files/README.md
+++ b/actix-files/README.md
@@ -3,17 +3,17 @@
> Static file serving for Actix Web
[](https://crates.io/crates/actix-files)
-[](https://docs.rs/actix-files/0.5.0)
+[](https://docs.rs/actix-files/0.6.0-beta.3)
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)

-[](https://deps.rs/crate/actix-files/0.5.0)
+[](https://deps.rs/crate/actix-files/0.6.0-beta.3)
[](https://crates.io/crates/actix-files)
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-files/)
-- [Example Project](https://github.com/actix/examples/tree/master/static_index)
+- [Example Project](https://github.com/actix/examples/tree/master/basics/static_index)
- [Chat on Gitter](https://gitter.im/actix/actix-web)
- Minimum supported Rust version: 1.46 or later
diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs
index 6f8b28bbf..292e3fdf3 100644
--- a/actix-files/src/files.rs
+++ b/actix-files/src/files.rs
@@ -19,7 +19,7 @@ use crate::{
///
/// `Files` service must be registered with `App::service()` method.
///
-/// ```rust
+/// ```
/// use actix_web::App;
/// use actix_files::Files;
///
diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs
index 04dd9f07f..018079b21 100644
--- a/actix-files/src/lib.rs
+++ b/actix-files/src/lib.rs
@@ -3,7 +3,7 @@
//! Provides a non-blocking service for serving static files from disk.
//!
//! # Example
-//! ```rust
+//! ```
//! use actix_web::App;
//! use actix_files::Files;
//!
@@ -662,8 +662,12 @@ mod tests {
#[actix_rt::test]
async fn test_static_files_bad_directory() {
- let _st: Files = Files::new("/", "missing");
- let _st: Files = Files::new("/", "Cargo.toml");
+ let service = Files::new("/", "./missing").new_service(()).await.unwrap();
+
+ let req = TestRequest::with_uri("/").to_srv_request();
+ let resp = test::call_service(&service, req).await;
+
+ assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[actix_rt::test]
@@ -676,75 +680,34 @@ mod tests {
.await
.unwrap();
let req = TestRequest::with_uri("/missing").to_srv_request();
-
let resp = test::call_service(&st, req).await;
+
assert_eq!(resp.status(), StatusCode::OK);
let bytes = test::read_body(resp).await;
assert_eq!(bytes, web::Bytes::from_static(b"default content"));
}
- // #[actix_rt::test]
- // async fn test_serve_index() {
- // let st = Files::new(".").index_file("test.binary");
- // let req = TestRequest::default().uri("/tests").finish();
+ #[actix_rt::test]
+ async fn test_serve_index_nested() {
+ let service = Files::new(".", ".")
+ .index_file("lib.rs")
+ .new_service(())
+ .await
+ .unwrap();
- // let resp = st.handle(&req).respond_to(&req).unwrap();
- // let resp = resp.as_msg();
- // assert_eq!(resp.status(), StatusCode::OK);
- // assert_eq!(
- // resp.headers()
- // .get(header::CONTENT_TYPE)
- // .expect("content type"),
- // "application/octet-stream"
- // );
- // assert_eq!(
- // resp.headers()
- // .get(header::CONTENT_DISPOSITION)
- // .expect("content disposition"),
- // "attachment; filename=\"test.binary\""
- // );
+ let req = TestRequest::default().uri("/src").to_srv_request();
+ let resp = test::call_service(&service, req).await;
- // let req = TestRequest::default().uri("/tests/").finish();
- // let resp = st.handle(&req).respond_to(&req).unwrap();
- // let resp = resp.as_msg();
- // assert_eq!(resp.status(), StatusCode::OK);
- // assert_eq!(
- // resp.headers().get(header::CONTENT_TYPE).unwrap(),
- // "application/octet-stream"
- // );
- // assert_eq!(
- // resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
- // "attachment; filename=\"test.binary\""
- // );
-
- // // nonexistent index file
- // let req = TestRequest::default().uri("/tests/unknown").finish();
- // let resp = st.handle(&req).respond_to(&req).unwrap();
- // let resp = resp.as_msg();
- // assert_eq!(resp.status(), StatusCode::NOT_FOUND);
-
- // let req = TestRequest::default().uri("/tests/unknown/").finish();
- // let resp = st.handle(&req).respond_to(&req).unwrap();
- // let resp = resp.as_msg();
- // assert_eq!(resp.status(), StatusCode::NOT_FOUND);
- // }
-
- // #[actix_rt::test]
- // async fn test_serve_index_nested() {
- // let st = Files::new(".").index_file("mod.rs");
- // let req = TestRequest::default().uri("/src/client").finish();
- // let resp = st.handle(&req).respond_to(&req).unwrap();
- // let resp = resp.as_msg();
- // assert_eq!(resp.status(), StatusCode::OK);
- // assert_eq!(
- // resp.headers().get(header::CONTENT_TYPE).unwrap(),
- // "text/x-rust"
- // );
- // assert_eq!(
- // resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
- // "inline; filename=\"mod.rs\""
- // );
- // }
+ assert_eq!(resp.status(), StatusCode::OK);
+ assert_eq!(
+ resp.headers().get(header::CONTENT_TYPE).unwrap(),
+ "text/x-rust"
+ );
+ assert_eq!(
+ resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
+ "inline; filename=\"lib.rs\""
+ );
+ }
#[actix_rt::test]
async fn integration_serve_index() {
diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs
index a688b2e6c..2846646a2 100644
--- a/actix-files/src/named.rs
+++ b/actix-files/src/named.rs
@@ -60,7 +60,7 @@ impl NamedFile {
///
/// # Examples
///
- /// ```rust
+ /// ```
/// use actix_files::NamedFile;
/// use std::io::{self, Write};
/// use std::env;
@@ -137,7 +137,7 @@ impl NamedFile {
///
/// # Examples
///
- /// ```rust
+ /// ```
/// use actix_files::NamedFile;
///
/// let file = NamedFile::open("foo.txt");
@@ -156,7 +156,7 @@ impl NamedFile {
///
/// # Examples
///
- /// ```rust
+ /// ```
/// # use std::io;
/// use actix_files::NamedFile;
///
diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs
index 14eea6ebc..3214963ed 100644
--- a/actix-files/src/service.rs
+++ b/actix-files/src/service.rs
@@ -1,4 +1,4 @@
-use std::{fmt, io, path::PathBuf, rc::Rc, task::Poll};
+use std::{fmt, io, path::PathBuf, rc::Rc};
use actix_service::Service;
use actix_web::{
diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md
index 2f47d700d..d6a2cdd9b 100644
--- a/actix-http-test/CHANGES.md
+++ b/actix-http-test/CHANGES.md
@@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx
+## 3.0.0-beta.3 - 2021-03-09
+* No notable changes.
+
+
## 3.0.0-beta.2 - 2021-02-10
* No notable changes.
diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml
index 6dcf73637..a7efc5310 100644
--- a/actix-http-test/Cargo.toml
+++ b/actix-http-test/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "actix-http-test"
-version = "3.0.0-beta.2"
+version = "3.0.0-beta.3"
authors = ["Nikolay Kim "]
description = "Various helpers for Actix applications to use during testing"
readme = "README.md"
@@ -31,18 +31,18 @@ openssl = ["tls-openssl", "awc/openssl"]
[dependencies]
actix-service = "2.0.0-beta.4"
actix-codec = "0.4.0-beta.1"
-actix-tls = "3.0.0-beta.3"
+actix-tls = "3.0.0-beta.4"
actix-utils = "3.0.0-beta.2"
-actix-rt = "2"
+actix-rt = "2.1"
actix-server = "2.0.0-beta.3"
-awc = "3.0.0-beta.2"
+awc = { version = "3.0.0-beta.3", default-features = false }
base64 = "0.13"
bytes = "1"
futures-core = { version = "0.3.7", default-features = false }
http = "0.2.2"
log = "0.4"
-socket2 = "0.3"
+socket2 = "0.4"
serde = "1.0"
serde_json = "1.0"
slab = "0.4"
@@ -50,12 +50,6 @@ serde_urlencoded = "0.7"
time = { version = "0.2.23", default-features = false, features = ["std"] }
tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
-[target.'cfg(windows)'.dependencies.tls-openssl]
-version = "0.10.9"
-package = "openssl"
-features = ["vendored"]
-optional = true
-
[dev-dependencies]
-actix-web = "4.0.0-beta.3"
-actix-http = "3.0.0-beta.3"
+actix-web = { version = "4.0.0-beta.4", default-features = false, features = ["cookies"] }
+actix-http = "3.0.0-beta.4"
diff --git a/actix-http-test/README.md b/actix-http-test/README.md
index 66f15979d..8cec94808 100644
--- a/actix-http-test/README.md
+++ b/actix-http-test/README.md
@@ -3,9 +3,9 @@
> Various helpers for Actix applications to use during testing.
[](https://crates.io/crates/actix-http-test)
-[](https://docs.rs/actix-http-test/2.1.0)
+[](https://docs.rs/actix-http-test/3.0.0-beta.3)

-[](https://deps.rs/crate/actix-http-test/2.1.0)
+[](https://deps.rs/crate/actix-http-test/3.0.0-beta.3)
[](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & Resources
diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs
index 8de07c8d3..3749b78ca 100644
--- a/actix-http-test/src/lib.rs
+++ b/actix-http-test/src/lib.rs
@@ -26,7 +26,7 @@ use socket2::{Domain, Protocol, Socket, Type};
///
/// # Examples
///
-/// ```rust
+/// ```
/// use actix_http::HttpService;
/// use actix_http_test::TestServer;
/// use actix_web::{web, App, HttpResponse, Error};
@@ -118,10 +118,10 @@ pub async fn test_server_with_addr>(
/// Get first available unused address
pub fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
- let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
+ let socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap();
socket.bind(&addr.into()).unwrap();
socket.set_reuse_address(true).unwrap();
- let tcp = socket.into_tcp_listener();
+ let tcp = net::TcpListener::from(socket);
tcp.local_addr().unwrap()
}
diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md
index 6ba111eb3..c4e0aec89 100644
--- a/actix-http/CHANGES.md
+++ b/actix-http/CHANGES.md
@@ -1,15 +1,32 @@
# Changes
## Unreleased - 2021-xx-xx
+### Added
+* `client::Connector::handshake_timeout` method for customize tls connection handshake timeout. [#2081]
+* `client::ConnectorService` as `client::Connector::finish` method's return type [#2081]
+* `client::ConnectionIo` trait alias [#2081]
+
+### Chaged
+* `client::Connector` type now only have one generic type for `actix_service::Service`. [#2063]
+
+[#2063]: https://github.com/actix/actix-web/pull/2063
+[#2081]: https://github.com/actix/actix-web/pull/2081
+
+
+## 3.0.0-beta.4 - 2021-03-08
### Changed
* Feature `cookies` is now optional and disabled by default. [#1981]
+* `ws::hash_key` now returns array. [#2035]
+* `ResponseBuilder::json` now takes `impl Serialize`. [#2052]
### Removed
-* re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994]
+* Re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994]
* `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994]
[#1981]: https://github.com/actix/actix-web/pull/1981
[#1994]: https://github.com/actix/actix-web/pull/1994
+[#2035]: https://github.com/actix/actix-web/pull/2035
+[#2052]: https://github.com/actix/actix-web/pull/2052
## 3.0.0-beta.3 - 2021-02-10
diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml
index 78fb55079..e1aebb76b 100644
--- a/actix-http/Cargo.toml
+++ b/actix-http/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "actix-http"
-version = "3.0.0-beta.3"
+version = "3.0.0-beta.4"
authors = ["Nikolay Kim "]
description = "HTTP primitives for the Actix ecosystem"
readme = "README.md"
@@ -47,8 +47,8 @@ trust-dns = ["trust-dns-resolver"]
actix-service = "2.0.0-beta.4"
actix-codec = "0.4.0-beta.1"
actix-utils = "3.0.0-beta.2"
-actix-rt = "2"
-actix-tls = "3.0.0-beta.2"
+actix-rt = "2.1"
+actix-tls = "3.0.0-beta.4"
ahash = "0.7"
base64 = "0.13"
@@ -61,12 +61,12 @@ derive_more = "0.99.5"
encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
-h2 = "0.3.0"
+h2 = "0.3.1"
http = "0.2.2"
httparse = "1.3"
itoa = "0.4"
language-tags = "0.2"
-lazy_static = "1.4"
+once_cell = "1.5"
log = "0.4"
mime = "0.3"
percent-encoding = "2.1"
@@ -89,8 +89,8 @@ trust-dns-resolver = { version = "0.20.0", optional = true }
[dev-dependencies]
actix-server = "2.0.0-beta.3"
-actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] }
-actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] }
+actix-http-test = { version = "3.0.0-beta.3", features = ["openssl"] }
+actix-tls = { version = "3.0.0-beta.4", features = ["openssl"] }
criterion = "0.3"
env_logger = "0.8"
rcgen = "0.8"
@@ -98,10 +98,9 @@ serde_derive = "1.0"
tls-openssl = { version = "0.10", package = "openssl" }
tls-rustls = { version = "0.19", package = "rustls" }
-[target.'cfg(windows)'.dev-dependencies.tls-openssl]
-version = "0.10.9"
-package = "openssl"
-features = ["vendored"]
+[[example]]
+name = "ws"
+required-features = ["rustls"]
[[bench]]
name = "write-camel-case"
diff --git a/actix-http/README.md b/actix-http/README.md
index 881fbc8c5..53fedd40e 100644
--- a/actix-http/README.md
+++ b/actix-http/README.md
@@ -3,11 +3,11 @@
> HTTP primitives for the Actix ecosystem.
[](https://crates.io/crates/actix-http)
-[](https://docs.rs/actix-http/3.0.0-beta.3)
+[](https://docs.rs/actix-http/3.0.0-beta.4)
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)

-[](https://deps.rs/crate/actix-http/3.0.0-beta.3)
+[](https://deps.rs/crate/actix-http/3.0.0-beta.4)
[](https://crates.io/crates/actix-http)
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs
index 90d768cbe..176ac5c2b 100644
--- a/actix-http/examples/echo.rs
+++ b/actix-http/examples/echo.rs
@@ -3,7 +3,7 @@ use std::{env, io};
use actix_http::{Error, HttpService, Request, Response};
use actix_server::Server;
use bytes::BytesMut;
-use futures_util::StreamExt;
+use futures_util::StreamExt as _;
use http::header::HeaderValue;
use log::info;
diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs
index bc932ce8f..408a40114 100644
--- a/actix-http/examples/echo2.rs
+++ b/actix-http/examples/echo2.rs
@@ -4,7 +4,7 @@ use actix_http::http::HeaderValue;
use actix_http::{Error, HttpService, Request, Response};
use actix_server::Server;
use bytes::BytesMut;
-use futures_util::StreamExt;
+use futures_util::StreamExt as _;
use log::info;
async fn handle_request(mut req: Request) -> Result {
diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs
new file mode 100644
index 000000000..4e03aa8ab
--- /dev/null
+++ b/actix-http/examples/ws.rs
@@ -0,0 +1,107 @@
+//! Sets up a WebSocket server over TCP and TLS.
+//! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames.
+
+extern crate tls_rustls as rustls;
+
+use std::{
+ env, io,
+ pin::Pin,
+ task::{Context, Poll},
+ time::Duration,
+};
+
+use actix_codec::Encoder;
+use actix_http::{error::Error, ws, HttpService, Request, Response};
+use actix_rt::time::{interval, Interval};
+use actix_server::Server;
+use bytes::{Bytes, BytesMut};
+use bytestring::ByteString;
+use futures_core::{ready, Stream};
+
+#[actix_rt::main]
+async fn main() -> io::Result<()> {
+ env::set_var("RUST_LOG", "actix=info,h2_ws=info");
+ env_logger::init();
+
+ Server::build()
+ .bind("tcp", ("127.0.0.1", 8080), || {
+ HttpService::build().h1(handler).tcp()
+ })?
+ .bind("tls", ("127.0.0.1", 8443), || {
+ HttpService::build().finish(handler).rustls(tls_config())
+ })?
+ .run()
+ .await
+}
+
+async fn handler(req: Request) -> Result {
+ log::info!("handshaking");
+ let mut res = ws::handshake(req.head())?;
+
+ // handshake will always fail under HTTP/2
+
+ log::info!("responding");
+ Ok(res.streaming(Heartbeat::new(ws::Codec::new())))
+}
+
+struct Heartbeat {
+ codec: ws::Codec,
+ interval: Interval,
+}
+
+impl Heartbeat {
+ fn new(codec: ws::Codec) -> Self {
+ Self {
+ codec,
+ interval: interval(Duration::from_secs(4)),
+ }
+ }
+}
+
+impl Stream for Heartbeat {
+ type Item = Result;
+
+ fn poll_next(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ ) -> Poll