diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..128f51ffd
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,37 @@
+---
+name: bug report
+about: create a bug report
+---
+
+Your issue may already be reported!
+Please search on the [Actix Web issue tracker](https://github.com/actix/actix-web/issues) before creating one.
+
+## Expected Behavior
+
+
+
+## Current Behavior
+
+
+
+## Possible Solution
+
+
+
+## Steps to Reproduce (for bugs)
+
+
+1.
+2.
+3.
+4.
+
+## Context
+
+
+
+## Your Environment
+
+
+* Rust Version (I.e, output of `rustc -V`):
+* Actix Web Version:
diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml
new file mode 100644
index 000000000..08bb81d48
--- /dev/null
+++ b/.github/workflows/bench.yml
@@ -0,0 +1,47 @@
+name: Benchmark (Linux)
+
+on: [push, pull_request]
+
+jobs:
+ check_benchmark:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@master
+
+ - name: Install Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: nightly
+ profile: minimal
+ override: true
+
+ - name: Generate Cargo.lock
+ uses: actions-rs/cargo@v1
+ with:
+ command: generate-lockfile
+ - name: Cache cargo registry
+ uses: actions/cache@v1
+ with:
+ path: ~/.cargo/registry
+ key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
+ - name: Cache cargo index
+ uses: actions/cache@v1
+ with:
+ path: ~/.cargo/git
+ key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
+ - name: Cache cargo build
+ uses: actions/cache@v1
+ with:
+ path: target
+ key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Check benchmark
+ uses: actions-rs/cargo@v1
+ with:
+ command: bench
+
+ - name: Clear the cargo caches
+ run: |
+ cargo install cargo-cache --no-default-features --features ci-autoclean
+ cargo-cache
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
new file mode 100644
index 000000000..7cabb8020
--- /dev/null
+++ b/.github/workflows/linux.yml
@@ -0,0 +1,90 @@
+name: CI (Linux)
+
+on: [push, pull_request]
+
+jobs:
+ build_and_test:
+ strategy:
+ fail-fast: false
+ matrix:
+ version:
+ - 1.39.0 # MSRV
+ - stable
+ - nightly
+
+ name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@master
+
+ - name: Install ${{ matrix.version }}
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
+ profile: minimal
+ override: true
+
+ - name: Generate Cargo.lock
+ uses: actions-rs/cargo@v1
+ with:
+ command: generate-lockfile
+ - name: Cache cargo registry
+ uses: actions/cache@v1
+ with:
+ path: ~/.cargo/registry
+ key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
+ - name: Cache cargo index
+ uses: actions/cache@v1
+ with:
+ path: ~/.cargo/git
+ key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
+ - name: Cache cargo build
+ uses: actions/cache@v1
+ with:
+ path: target
+ key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: check build
+ uses: actions-rs/cargo@v1
+ with:
+ command: check
+ args: --all --bins --examples --tests
+
+ - name: tests
+ uses: actions-rs/cargo@v1
+ timeout-minutes: 40
+ with:
+ command: test
+ args: --all --all-features --no-fail-fast -- --nocapture
+
+ - name: tests (actix-http)
+ uses: actions-rs/cargo@v1
+ timeout-minutes: 40
+ with:
+ command: test
+ args: --package=actix-http --no-default-features --features=rustls -- --nocapture
+
+ - name: tests (awc)
+ uses: actions-rs/cargo@v1
+ timeout-minutes: 40
+ with:
+ command: test
+ args: --package=awc --no-default-features --features=rustls -- --nocapture
+
+ - name: Generate coverage file
+ if: matrix.version == 'stable' && github.ref == 'refs/heads/master'
+ run: |
+ cargo install cargo-tarpaulin
+ cargo tarpaulin --out Xml
+ - name: Upload to Codecov
+ if: matrix.version == 'stable' && github.ref == 'refs/heads/master'
+ uses: codecov/codecov-action@v1
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ file: cobertura.xml
+
+ - name: Clear the cargo caches
+ run: |
+ cargo install cargo-cache --no-default-features --features ci-autoclean
+ cargo-cache
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index f50ae2f05..397236a29 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -27,22 +27,22 @@ jobs:
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with:
- command: update
+ command: generate-lockfile
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
- key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
+ key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
- key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-index-${{ hashFiles('**/Cargo.lock') }}
+ key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v1
with:
path: target
- key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-build-${{ hashFiles('**/Cargo.lock') }}
+ key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: check build
uses: actions-rs/cargo@v1
@@ -57,3 +57,8 @@ jobs:
args: --all --all-features --no-fail-fast -- --nocapture
--skip=test_h2_content_length
--skip=test_reading_deflate_encoding_large_random_rustls
+
+ - name: Clear the cargo caches
+ run: |
+ cargo install cargo-cache --no-default-features --features ci-autoclean
+ cargo-cache
diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml
new file mode 100644
index 000000000..388ae3704
--- /dev/null
+++ b/.github/workflows/upload-doc.yml
@@ -0,0 +1,35 @@
+name: Upload documentation
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ if: github.repository == 'actix/actix-web'
+
+ steps:
+ - uses: actions/checkout@master
+
+ - name: Install Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable-x86_64-unknown-linux-gnu
+ profile: minimal
+ override: true
+
+ - name: check build
+ uses: actions-rs/cargo@v1
+ with:
+ command: doc
+ args: --no-deps --all-features
+
+ - name: Tweak HTML
+ run: echo "" > target/doc/index.html
+
+ - name: Upload documentation
+ run: |
+ git clone https://github.com/davisp/ghp-import.git
+ ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://${{ secrets.GITHUB_TOKEN }}@github.com/"${{ github.repository }}.git" target/doc
\ No newline at end of file
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 9aa3d3ba4..5fd785fad 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -27,37 +27,14 @@ jobs:
profile: minimal
override: true
- - name: Generate Cargo.lock
- uses: actions-rs/cargo@v1
- with:
- command: update
- - name: Cache cargo registry
- uses: actions/cache@v1
- with:
- path: ~/.cargo/registry
- key: ${{ matrix.version }}-x86_64-pc-windows-msvc-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- - name: Cache cargo index
- uses: actions/cache@v1
- with:
- path: ~/.cargo/git
- key: ${{ matrix.version }}-x86_64-pc-windows-msvc-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- - name: Cache cargo build
- uses: actions/cache@v1
- with:
- path: target
- key: ${{ matrix.version }}-x86_64-pc-windows-msvc-cargo-build-${{ hashFiles('**/Cargo.lock') }}
- - name: Cache vcpkg package
- uses: actions/cache@v1
- id: cache-vcpkg
- with:
- path: C:\vcpkg
- key: windows_x64-${{ matrix.version }}-vcpkg
-
- name: Install OpenSSL
- if: steps.cache-vcpkg.outputs.cache-hit != 'true'
run: |
vcpkg integrate install
vcpkg install openssl:x64-windows
+ Copy-Item C:\vcpkg\installed\x64-windows\bin\libcrypto-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libcrypto.dll
+ Copy-Item C:\vcpkg\installed\x64-windows\bin\libssl-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libssl.dll
+ Get-ChildItem C:\vcpkg\installed\x64-windows\bin
+ Get-ChildItem C:\vcpkg\installed\x64-windows\lib
- name: check build
uses: actions-rs/cargo@v1
@@ -76,4 +53,7 @@ jobs:
--skip=test_simple
--skip=test_expect_continue
--skip=test_http10_keepalive
- --skip=test_slow_request
+ --skip=test_slow_request
+ --skip=test_connection_force_close
+ --skip=test_connection_server_close
+ --skip=test_connection_wait_queue_force_close
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index f10f82a48..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,61 +0,0 @@
-language: rust
-sudo: required
-dist: trusty
-
-cache:
- # cargo: true
- apt: true
-
-matrix:
- include:
- - rust: stable
- - rust: beta
- - rust: nightly-2019-11-20
- allow_failures:
- - rust: nightly-2019-11-20
-
-env:
- global:
- # - RUSTFLAGS="-C link-dead-code"
- - OPENSSL_VERSION=openssl-1.0.2
-
-before_install:
- - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl
- - sudo apt-get update -qq
- - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
-
-before_cache: |
- if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-11-20" ]]; then
- RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --version 0.6.11 cargo-tarpaulin
- fi
-
-# Add clippy
-before_script:
- - export PATH=$PATH:~/.cargo/bin
-
-script:
- - cargo update
- - cargo check --all --no-default-features
- - |
- if [[ "$TRAVIS_RUST_VERSION" == "stable" || "$TRAVIS_RUST_VERSION" == "beta" ]]; then
- cargo test --all-features --all -- --nocapture
- cd actix-http; cargo test --no-default-features --features="rustls" -- --nocapture; cd ..
- cd awc; cargo test --no-default-features --features="rustls" -- --nocapture; cd ..
- fi
-
-# Upload docs
-after_success:
- - |
- if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
- cargo doc --no-deps --all-features &&
- echo "" > target/doc/index.html &&
- git clone https://github.com/davisp/ghp-import.git &&
- ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
- echo "Uploaded documentation"
- fi
- - |
- if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-11-20" ]]; then
- taskset -c 0 cargo tarpaulin --out Xml --all --all-features
- bash <(curl -s https://codecov.io/bash)
- echo "Uploaded code coverage"
- fi
diff --git a/CHANGES.md b/CHANGES.md
index 29f78e0b1..da4a77a80 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,10 +1,19 @@
# Changes
+
## [2.0.NEXT] - 2020-01-xx
+### Added
+
+* Add helper function for creating routes with `TRACE` method guard `web::trace()`
+
### Changed
-* Use `sha-1` crate instead of unmaintained `sha1` crate
+* Use `sha-1` crate instead of unmaintained `sha1` crate
+* Skip empty chunks when returning response from a `Stream` #1308
+* Update the `time` dependency to 0.2.7
+* Update `actix-tls` dependency to 2.0.0-alpha.1
+* Update `rustls` dependency to 0.17
## [2.0.0] - 2019-12-25
diff --git a/Cargo.toml b/Cargo.toml
index 9e1b559eb..0cb0506ca 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,8 +33,8 @@ members = [
"actix-cors",
"actix-files",
"actix-framed",
- "actix-session",
- "actix-identity",
+# "actix-session",
+# "actix-identity",
"actix-multipart",
"actix-web-actors",
"actix-web-codegen",
@@ -68,10 +68,10 @@ actix-server = "1.0.0"
actix-testing = "1.0.0"
actix-macros = "0.1.0"
actix-threadpool = "0.3.1"
-actix-tls = "1.0.0"
+actix-tls = "2.0.0-alpha.1"
actix-web-codegen = "0.2.0"
-actix-http = "1.0.1"
+actix-http = "2.0.0-alpha.2"
awc = { version = "1.0.1", default-features = false }
bytes = "0.5.3"
@@ -87,18 +87,19 @@ regex = "1.3"
serde = { version = "1.0", features=["derive"] }
serde_json = "1.0"
serde_urlencoded = "0.6.1"
-time = "0.1.42"
+time = { version = "0.2.7", default-features = false, features = ["std"] }
url = "2.1"
open-ssl = { version="0.10", package = "openssl", optional = true }
-rust-tls = { version = "0.16.0", package = "rustls", optional = true }
+rust-tls = { version = "0.17.0", package = "rustls", optional = true }
[dev-dependencies]
-actix = "0.9.0"
+actix = "0.10.0-alpha.1"
rand = "0.7"
env_logger = "0.6"
serde_derive = "1.0"
brotli2 = "0.3.2"
flate2 = "1.0.13"
+criterion = "0.3"
[profile.release]
lto = true
@@ -116,3 +117,11 @@ actix-session = { path = "actix-session" }
actix-files = { path = "actix-files" }
actix-multipart = { path = "actix-multipart" }
awc = { path = "awc" }
+
+[[bench]]
+name = "server"
+harness = false
+
+[[bench]]
+name = "service"
+harness = false
diff --git a/MIGRATION.md b/MIGRATION.md
index 529f9714d..86721e0eb 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -1,3 +1,11 @@
+## Unreleased
+
+* Setting a cookie's SameSite property, explicitly, to `SameSite::None` will now
+ result in `SameSite=None` being sent with the response Set-Cookie header.
+ To create a cookie without a SameSite attribute, remove any calls setting same_site.
+* actix-http support for Actors messages was moved to actix-http crate and is enabled
+ with feature `actors`
+
## 2.0.0
* `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to
diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml
index 104eb3dfa..c37d023f6 100644
--- a/actix-files/Cargo.toml
+++ b/actix-files/Cargo.toml
@@ -19,7 +19,7 @@ path = "src/lib.rs"
[dependencies]
actix-web = { version = "2.0.0-rc", default-features = false }
-actix-http = "1.0.1"
+actix-http = "2.0.0-alpha.2"
actix-service = "1.0.1"
bitflags = "1"
bytes = "0.5.3"
diff --git a/actix-files/src/error.rs b/actix-files/src/error.rs
index 49a46e58d..9b30cbaa2 100644
--- a/actix-files/src/error.rs
+++ b/actix-files/src/error.rs
@@ -5,6 +5,7 @@ use derive_more::Display;
#[derive(Display, Debug, PartialEq)]
pub enum FilesError {
/// Path is not a directory
+ #[allow(dead_code)]
#[display(fmt = "Path is not a directory. Unable to serve static files")]
IsNotDirectory,
diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml
index 7e322e1d4..4389fe69b 100644
--- a/actix-framed/Cargo.toml
+++ b/actix-framed/Cargo.toml
@@ -23,7 +23,7 @@ actix-codec = "0.2.0"
actix-service = "1.0.1"
actix-router = "0.2.1"
actix-rt = "1.0.0"
-actix-http = "1.0.1"
+actix-http = "2.0.0-alpha.2"
bytes = "0.5.3"
futures = "0.3.1"
@@ -32,6 +32,6 @@ log = "0.4"
[dev-dependencies]
actix-server = "1.0.0"
-actix-connect = { version = "1.0.0", features=["openssl"] }
+actix-connect = { version = "2.0.0-alpha.1", features=["openssl"] }
actix-http-test = { version = "1.0.0", features=["openssl"] }
actix-utils = "1.0.3"
diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md
index 1c8e4f053..fb1c3a329 100644
--- a/actix-http/CHANGES.md
+++ b/actix-http/CHANGES.md
@@ -1,5 +1,34 @@
# Changes
+## [2.0.0-alpha.2] - 2020-03-07
+
+### Changed
+
+* Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395]
+
+* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB respectively
+ to improve download speed for awc when downloading large objects. [#1394]
+
+* client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394]
+
+* client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394]
+
+[#1394]: https://github.com/actix/actix-web/pull/1394
+[#1395]: https://github.com/actix/actix-web/pull/1395
+
+## [2.0.0-alpha.1] - 2020-02-27
+
+### Changed
+
+* Update the `time` dependency to 0.2.7.
+* Moved actors messages support from actix crate, enabled with feature `actors`.
+* Breaking change: trait MessageBody requires Unpin and accepting Pin<&mut Self> instead of &mut self in the poll_next().
+* MessageBody is not implemented for &'static [u8] anymore.
+
+### Fixed
+
+* Allow `SameSite=None` cookies to be sent in a response.
+
## [1.0.1] - 2019-12-20
### Fixed
diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml
index 93aaa756e..c8b6d7d7d 100644
--- a/actix-http/Cargo.toml
+++ b/actix-http/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "actix-http"
-version = "1.0.1"
+version = "2.0.0-alpha.2"
authors = ["Nikolay Kim "]
description = "Actix http primitives"
readme = "README.md"
@@ -15,7 +15,7 @@ license = "MIT/Apache-2.0"
edition = "2018"
[package.metadata.docs.rs]
-features = ["openssl", "rustls", "failure", "compress", "secure-cookies"]
+features = ["openssl", "rustls", "failure", "compress", "secure-cookies","actors"]
[lib]
name = "actix_http"
@@ -39,20 +39,23 @@ failure = ["fail-ure"]
# support for secure cookies
secure-cookies = ["ring"]
+# support for actix Actor messages
+actors = ["actix"]
+
[dependencies]
-actix-service = "1.0.1"
+actix-service = "1.0.5"
actix-codec = "0.2.0"
-actix-connect = "1.0.1"
-actix-utils = "1.0.3"
+actix-connect = "2.0.0-alpha.1"
+actix-utils = "1.0.6"
actix-rt = "1.0.0"
actix-threadpool = "0.3.1"
-actix-tls = { version = "1.0.0", optional = true }
+actix-tls = { version = "2.0.0-alpha.1", optional = true }
+actix = { version = "0.10.0-alpha.1", optional = true }
base64 = "0.11"
bitflags = "1.2"
bytes = "0.5.3"
copyless = "0.1.4"
-chrono = "0.4.6"
derive_more = "0.99.2"
either = "1.5.3"
encoding_rs = "0.8"
@@ -77,7 +80,7 @@ serde_json = "1.0"
sha-1 = "0.8"
slab = "0.4"
serde_urlencoded = "0.6.1"
-time = "0.1.42"
+time = { version = "0.2.7", default-features = false, features = ["std"] }
# for secure cookie
ring = { version = "0.16.9", optional = true }
@@ -90,12 +93,17 @@ flate2 = { version = "1.0.13", optional = true }
fail-ure = { version = "0.1.5", package="failure", optional = true }
[dev-dependencies]
-actix-server = "1.0.0"
-actix-connect = { version = "1.0.0", features=["openssl"] }
+actix-server = "1.0.1"
+actix-connect = { version = "2.0.0-alpha.1", features=["openssl"] }
actix-http-test = { version = "1.0.0", features=["openssl"] }
-actix-tls = { version = "1.0.0", features=["openssl"] }
+actix-tls = { version = "2.0.0-alpha.1", features=["openssl"] }
+criterion = "0.3"
futures = "0.3.1"
-env_logger = "0.6"
+env_logger = "0.7"
serde_derive = "1.0"
open-ssl = { version="0.10", package = "openssl" }
-rust-tls = { version="0.16", package = "rustls" }
+rust-tls = { version="0.17", package = "rustls" }
+
+[[bench]]
+name = "content-length"
+harness = false
diff --git a/actix-http/README.md b/actix-http/README.md
index d75e822ba..9acad3e6d 100644
--- a/actix-http/README.md
+++ b/actix-http/README.md
@@ -14,19 +14,34 @@ Actix http
```rust
// see examples/framed_hello.rs for complete list of used crates.
-extern crate actix_http;
-use actix_http::{h1, Response, ServiceConfig};
+use std::{env, io};
-fn main() {
- Server::new().bind("framed_hello", "127.0.0.1:8080", || {
- IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())) // <- create h1 codec
- .and_then(TakeItem::new().map_err(|_| ())) // <- read one request
- .and_then(|(_req, _framed): (_, Framed<_, _>)| { // <- send response and close conn
- SendResponse::send(_framed, Response::Ok().body("Hello world!"))
- .map_err(|_| ())
- .map(|_| ())
- })
- }).unwrap().run();
+use actix_http::{HttpService, Response};
+use actix_server::Server;
+use futures::future;
+use http::header::HeaderValue;
+use log::info;
+
+#[actix_rt::main]
+async fn main() -> io::Result<()> {
+ env::set_var("RUST_LOG", "hello_world=info");
+ env_logger::init();
+
+ Server::build()
+ .bind("hello-world", "127.0.0.1:8080", || {
+ HttpService::build()
+ .client_timeout(1000)
+ .client_disconnect(1000)
+ .finish(|_req| {
+ info!("{:?}", _req);
+ let mut res = Response::Ok();
+ res.header("x-head", HeaderValue::from_static("dummy value!"));
+ future::ok::<_, ()>(res.body("Hello world!"))
+ })
+ .tcp()
+ })?
+ .run()
+ .await
}
```
diff --git a/actix-http/benches/content-length.rs b/actix-http/benches/content-length.rs
new file mode 100644
index 000000000..b001b3931
--- /dev/null
+++ b/actix-http/benches/content-length.rs
@@ -0,0 +1,267 @@
+use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
+
+use bytes::BytesMut;
+
+// benchmark sending all requests at the same time
+fn bench_write_content_length(c: &mut Criterion) {
+ let mut group = c.benchmark_group("write_content_length");
+
+ let sizes = [
+ 0, 1, 11, 83, 101, 653, 1001, 6323, 10001, 56329, 100001, 123456, 98724245,
+ 4294967202,
+ ];
+
+ for i in sizes.iter() {
+ group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| {
+ b.iter(|| {
+ let mut b = BytesMut::with_capacity(35);
+ _original::write_content_length(i, &mut b)
+ })
+ });
+
+ group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| {
+ b.iter(|| {
+ let mut b = BytesMut::with_capacity(35);
+ _new::write_content_length(i, &mut b)
+ })
+ });
+ }
+
+ group.finish();
+}
+
+criterion_group!(benches, bench_write_content_length);
+criterion_main!(benches);
+
+mod _new {
+ use bytes::{BufMut, BytesMut};
+
+ const DIGITS_START: u8 = b'0';
+
+ /// NOTE: bytes object has to contain enough space
+ pub fn write_content_length(n: usize, bytes: &mut BytesMut) {
+ if n == 0 {
+ bytes.put_slice(b"\r\ncontent-length: 0\r\n");
+ return;
+ }
+
+ bytes.put_slice(b"\r\ncontent-length: ");
+
+ if n < 10 {
+ bytes.put_u8(DIGITS_START + (n as u8));
+ } else if n < 100 {
+ let n = n as u8;
+
+ let d10 = n / 10;
+ let d1 = n % 10;
+
+ bytes.put_u8(DIGITS_START + d10);
+ bytes.put_u8(DIGITS_START + d1);
+ } else if n < 1000 {
+ let n = n as u16;
+
+ let d100 = (n / 100) as u8;
+ let d10 = ((n / 10) % 10) as u8;
+ let d1 = (n % 10) as u8;
+
+ bytes.put_u8(DIGITS_START + d100);
+ bytes.put_u8(DIGITS_START + d10);
+ bytes.put_u8(DIGITS_START + d1);
+ } else if n < 10_000 {
+ let n = n as u16;
+
+ let d1000 = (n / 1000) as u8;
+ let d100 = ((n / 100) % 10) as u8;
+ let d10 = ((n / 10) % 10) as u8;
+ let d1 = (n % 10) as u8;
+
+ bytes.put_u8(DIGITS_START + d1000);
+ bytes.put_u8(DIGITS_START + d100);
+ bytes.put_u8(DIGITS_START + d10);
+ bytes.put_u8(DIGITS_START + d1);
+ } else if n < 100_000 {
+ let n = n as u32;
+
+ let d10000 = (n / 10000) as u8;
+ let d1000 = ((n / 1000) % 10) as u8;
+ let d100 = ((n / 100) % 10) as u8;
+ let d10 = ((n / 10) % 10) as u8;
+ let d1 = (n % 10) as u8;
+
+ bytes.put_u8(DIGITS_START + d10000);
+ bytes.put_u8(DIGITS_START + d1000);
+ bytes.put_u8(DIGITS_START + d100);
+ bytes.put_u8(DIGITS_START + d10);
+ bytes.put_u8(DIGITS_START + d1);
+ } else if n < 1_000_000 {
+ let n = n as u32;
+
+ let d100000 = (n / 100000) as u8;
+ let d10000 = ((n / 10000) % 10) as u8;
+ let d1000 = ((n / 1000) % 10) as u8;
+ let d100 = ((n / 100) % 10) as u8;
+ let d10 = ((n / 10) % 10) as u8;
+ let d1 = (n % 10) as u8;
+
+ bytes.put_u8(DIGITS_START + d100000);
+ bytes.put_u8(DIGITS_START + d10000);
+ bytes.put_u8(DIGITS_START + d1000);
+ bytes.put_u8(DIGITS_START + d100);
+ bytes.put_u8(DIGITS_START + d10);
+ bytes.put_u8(DIGITS_START + d1);
+ } else {
+ write_usize(n, bytes);
+ }
+
+ bytes.put_slice(b"\r\n");
+ }
+
+ fn write_usize(n: usize, bytes: &mut BytesMut) {
+ let mut n = n;
+
+ // 20 chars is max length of a usize (2^64)
+ // digits will be added to the buffer from lsd to msd
+ let mut buf = BytesMut::with_capacity(20);
+
+ while n > 9 {
+ // "pop" the least-significant digit
+ let lsd = (n % 10) as u8;
+
+ // remove the lsd from n
+ n = n / 10;
+
+ buf.put_u8(DIGITS_START + lsd);
+ }
+
+ // put msd to result buffer
+ bytes.put_u8(DIGITS_START + (n as u8));
+
+ // put, in reverse (msd to lsd), remaining digits to buffer
+ for i in (0..buf.len()).rev() {
+ bytes.put_u8(buf[i]);
+ }
+ }
+}
+
+mod _original {
+ use std::{mem, ptr, slice};
+
+ use bytes::{BufMut, BytesMut};
+
+ const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
+ 2021222324252627282930313233343536373839\
+ 4041424344454647484950515253545556575859\
+ 6061626364656667686970717273747576777879\
+ 8081828384858687888990919293949596979899";
+
+ /// NOTE: bytes object has to contain enough space
+ pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
+ if n < 10 {
+ let mut buf: [u8; 21] = [
+ b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l',
+ b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'\r', b'\n',
+ ];
+ buf[18] = (n as u8) + b'0';
+ bytes.put_slice(&buf);
+ } else if n < 100 {
+ let mut buf: [u8; 22] = [
+ b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l',
+ b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'\r', b'\n',
+ ];
+ let d1 = n << 1;
+ unsafe {
+ ptr::copy_nonoverlapping(
+ DEC_DIGITS_LUT.as_ptr().add(d1),
+ buf.as_mut_ptr().offset(18),
+ 2,
+ );
+ }
+ bytes.put_slice(&buf);
+ } else if n < 1000 {
+ let mut buf: [u8; 23] = [
+ b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l',
+ b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'0', b'\r',
+ b'\n',
+ ];
+ // decode 2 more chars, if > 2 chars
+ let d1 = (n % 100) << 1;
+ n /= 100;
+ unsafe {
+ ptr::copy_nonoverlapping(
+ DEC_DIGITS_LUT.as_ptr().add(d1),
+ buf.as_mut_ptr().offset(19),
+ 2,
+ )
+ };
+
+ // decode last 1
+ buf[18] = (n as u8) + b'0';
+
+ bytes.put_slice(&buf);
+ } else {
+ bytes.put_slice(b"\r\ncontent-length: ");
+ convert_usize(n, bytes);
+ }
+ }
+
+ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
+ let mut curr: isize = 39;
+ let mut buf: [u8; 41] = unsafe { mem::MaybeUninit::uninit().assume_init() };
+ buf[39] = b'\r';
+ buf[40] = b'\n';
+ let buf_ptr = buf.as_mut_ptr();
+ let lut_ptr = DEC_DIGITS_LUT.as_ptr();
+
+ // eagerly decode 4 characters at a time
+ while n >= 10_000 {
+ let rem = (n % 10_000) as isize;
+ n /= 10_000;
+
+ let d1 = (rem / 100) << 1;
+ let d2 = (rem % 100) << 1;
+ curr -= 4;
+ unsafe {
+ ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
+ ptr::copy_nonoverlapping(
+ lut_ptr.offset(d2),
+ buf_ptr.offset(curr + 2),
+ 2,
+ );
+ }
+ }
+
+ // if we reach here numbers are <= 9999, so at most 4 chars long
+ let mut n = n as isize; // possibly reduce 64bit math
+
+ // decode 2 more chars, if > 2 chars
+ if n >= 100 {
+ let d1 = (n % 100) << 1;
+ n /= 100;
+ curr -= 2;
+ unsafe {
+ ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
+ }
+ }
+
+ // decode last 1 or 2 chars
+ if n < 10 {
+ curr -= 1;
+ unsafe {
+ *buf_ptr.offset(curr) = (n as u8) + b'0';
+ }
+ } else {
+ let d1 = n << 1;
+ curr -= 2;
+ unsafe {
+ ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
+ }
+ }
+
+ unsafe {
+ bytes.extend_from_slice(slice::from_raw_parts(
+ buf_ptr.offset(curr),
+ 41 - curr as usize,
+ ));
+ }
+ }
+}
diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs
index 3d57a472a..b2b88a7ea 100644
--- a/actix-http/examples/echo.rs
+++ b/actix-http/examples/echo.rs
@@ -17,23 +17,18 @@ async fn main() -> io::Result<()> {
HttpService::build()
.client_timeout(1000)
.client_disconnect(1000)
- .finish(|mut req: Request| {
- async move {
- let mut body = BytesMut::new();
- while let Some(item) = req.payload().next().await {
- body.extend_from_slice(&item?);
- }
-
- info!("request body: {:?}", body);
- Ok::<_, Error>(
- Response::Ok()
- .header(
- "x-head",
- HeaderValue::from_static("dummy value!"),
- )
- .body(body),
- )
+ .finish(|mut req: Request| async move {
+ let mut body = BytesMut::new();
+ while let Some(item) = req.payload().next().await {
+ body.extend_from_slice(&item?);
}
+
+ info!("request body: {:?}", body);
+ Ok::<_, Error>(
+ Response::Ok()
+ .header("x-head", HeaderValue::from_static("dummy value!"))
+ .body(body),
+ )
})
.tcp()
})?
diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs
index 850f97ee4..c581db604 100644
--- a/actix-http/src/body.rs
+++ b/actix-http/src/body.rs
@@ -5,6 +5,7 @@ use std::{fmt, mem};
use bytes::{Bytes, BytesMut};
use futures_core::Stream;
+use futures_util::ready;
use pin_project::{pin_project, project};
use crate::error::Error;
@@ -35,33 +36,46 @@ impl BodySize {
pub trait MessageBody {
fn size(&self) -> BodySize;
- fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll