diff --git a/.appveyor.yml b/.appveyor.yml
deleted file mode 100644
index 2f0a4a7dd..000000000
--- a/.appveyor.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-environment:
- global:
- PROJECT_NAME: actix-web
- matrix:
- # Stable channel
- - TARGET: i686-pc-windows-msvc
- CHANNEL: stable
- - TARGET: x86_64-pc-windows-gnu
- CHANNEL: stable
- - TARGET: x86_64-pc-windows-msvc
- CHANNEL: stable
- # Nightly channel
- - TARGET: i686-pc-windows-msvc
- CHANNEL: nightly
- - TARGET: x86_64-pc-windows-gnu
- CHANNEL: nightly
- - TARGET: x86_64-pc-windows-msvc
- CHANNEL: nightly
-
-# Install Rust and Cargo
-# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
-install:
- - ps: >-
- If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
- $Env:PATH += ';C:\msys64\mingw64\bin'
- } ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
- $Env:PATH += ';C:\MinGW\bin'
- }
- - curl -sSf -o rustup-init.exe https://win.rustup.rs
- - rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
- - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- - rustc -Vv
- - cargo -V
-
-# 'cargo test' takes care of building for us, so disable Appveyor's build stage.
-build: false
-
-# Equivalent to Travis' `script` phase
-test_script:
- - cargo clean
- - cargo test --no-default-features --features="flate2-rust"
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..3e62958d8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: true
+contact_links:
+ - name: Gitter channel (actix-web)
+ url: https://gitter.im/actix/actix-web
+ about: Please ask and answer questions about the actix-web here.
+ - name: Gitter channel (actix)
+ url: https://gitter.im/actix/actix
+ about: Please ask and answer questions about the actix here.
diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml
index 08bb81d48..ce8a7da7e 100644
--- a/.github/workflows/bench.yml
+++ b/.github/workflows/bench.yml
@@ -16,32 +16,8 @@ jobs:
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
+ args: --bench=server -- --sample-size=15
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
new file mode 100644
index 000000000..ae804cc53
--- /dev/null
+++ b/.github/workflows/linux.yml
@@ -0,0 +1,64 @@
+name: CI (Linux)
+
+on: [push, pull_request]
+
+jobs:
+ build_and_test:
+ strategy:
+ fail-fast: false
+ matrix:
+ version:
+ - 1.40.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: 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' || github.event_name == 'pull_request')
+ run: |
+ cargo install cargo-tarpaulin
+ cargo tarpaulin --out Xml
+ - name: Upload to Codecov
+ if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
+ uses: codecov/codecov-action@v1
+ with:
+ file: cobertura.xml
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 397236a29..6c360bacc 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -24,26 +24,6 @@ jobs:
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-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-trimmed-${{ hashFiles('**/Cargo.lock') }}
- - name: Cache cargo build
- uses: actions/cache@v1
- with:
- path: target
- key: ${{ matrix.version }}-x86_64-apple-darwin-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
-
- name: check build
uses: actions-rs/cargo@v1
with:
@@ -57,8 +37,3 @@ 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..75d534b28
--- /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 --workspace --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
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 36b224ba6..5fd785fad 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -56,3 +56,4 @@ jobs:
--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 b42635b86..739f1b13c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,15 +1,55 @@
# Changes
+## [3.0.0-alpha.3] - 2020-05-21
-## [2.0.NEXT] - 2020-01-xx
+### Added
+
+* Add option to create `Data` from `Arc` [#1509]
+
+### Changed
+
+* Resources and Scopes can now access non-overridden data types set on App (or containing scopes) when setting their own data. [#1486]
+
+* Fix audit issue logging by default peer address [#1485]
+
+* Bump minimum supported Rust version to 1.40
+
+* Replace deprecated `net2` crate with `socket2`
+
+[#1485]: https://github.com/actix/actix-web/pull/1485
+[#1509]: https://github.com/actix/actix-web/pull/1509
+
+## [3.0.0-alpha.2] - 2020-05-08
+
+### Changed
+
+* `{Resource,Scope}::default_service(f)` handlers now support app data extraction. [#1452]
+* Implement `std::error::Error` for our custom errors [#1422]
+* NormalizePath middleware now appends trailing / so that routes of form /example/ respond to /example requests. [#1433]
+* Remove the `failure` feature and support.
+
+[#1422]: https://github.com/actix/actix-web/pull/1422
+[#1433]: https://github.com/actix/actix-web/pull/1433
+[#1452]: https://github.com/actix/actix-web/pull/1452
+[#1486]: https://github.com/actix/actix-web/pull/1486
+
+
+## [3.0.0-alpha.1] - 2020-03-11
+
+### Added
+
+* Add helper function for creating routes with `TRACE` method guard `web::trace()`
+* Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing.
### Changed
* 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
-* Skip empty chunks when returning response from a `Stream` #1308
-
-* Update the `time` dependency to 0.2.5
+[#1308]: https://github.com/actix/actix-web/pull/1308
## [2.0.0] - 2019-12-25
diff --git a/Cargo.toml b/Cargo.toml
index a6783a6db..8c6d461d6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "actix-web"
-version = "2.0.0"
+version = "3.0.0-alpha.3"
authors = ["Nikolay Kim "]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
@@ -30,11 +30,7 @@ members = [
".",
"awc",
"actix-http",
- "actix-cors",
"actix-files",
- "actix-framed",
- "actix-session",
- "actix-identity",
"actix-multipart",
"actix-web-actors",
"actix-web-codegen",
@@ -42,7 +38,7 @@ members = [
]
[features]
-default = ["compress", "failure"]
+default = ["compress"]
# content-encoding support
compress = ["actix-http/compress", "awc/compress"]
@@ -50,14 +46,24 @@ compress = ["actix-http/compress", "awc/compress"]
# sessions feature, session require "ring" crate and c compiler
secure-cookies = ["actix-http/secure-cookies"]
-failure = ["actix-http/failure"]
-
# openssl
openssl = ["actix-tls/openssl", "awc/openssl", "open-ssl"]
# rustls
rustls = ["actix-tls/rustls", "awc/rustls", "rust-tls"]
+[[example]]
+name = "basic"
+required-features = ["compress"]
+
+[[example]]
+name = "uds"
+required-features = ["compress"]
+
+[[test]]
+name = "test_server"
+required-features = ["compress"]
+
[dependencies]
actix-codec = "0.2.0"
actix-service = "1.0.2"
@@ -68,34 +74,37 @@ 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"
-awc = { version = "1.0.1", default-features = false }
+actix-web-codegen = "0.2.2"
+actix-http = "2.0.0-alpha.4"
+awc = { version = "2.0.0-alpha.2", default-features = false }
bytes = "0.5.3"
derive_more = "0.99.2"
encoding_rs = "0.8"
-futures = "0.3.1"
+futures-channel = { version = "0.3.5", default-features = false }
+futures-core = { version = "0.3.5", default-features = false }
+futures-util = { version = "0.3.5", default-features = false }
fxhash = "0.2.1"
log = "0.4"
mime = "0.3"
-net2 = "0.2.33"
-pin-project = "0.4.6"
+socket2 = "0.3"
+pin-project = "0.4.17"
regex = "1.3"
serde = { version = "1.0", features=["derive"] }
serde_json = "1.0"
serde_urlencoded = "0.6.1"
-time = { version = "0.2.5", default-features = false, features = ["std"] }
+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 }
+tinyvec = { version = "0.3", features = ["alloc"] }
[dev-dependencies]
-actix = "0.9.0"
+actix = "0.10.0-alpha.1"
rand = "0.7"
-env_logger = "0.6"
+env_logger = "0.7"
serde_derive = "1.0"
brotli2 = "0.3.2"
flate2 = "1.0.13"
@@ -111,9 +120,6 @@ actix-web = { path = "." }
actix-http = { path = "actix-http" }
actix-http-test = { path = "test-server" }
actix-web-codegen = { path = "actix-web-codegen" }
-actix-cors = { path = "actix-cors" }
-actix-identity = { path = "actix-identity" }
-actix-session = { path = "actix-session" }
actix-files = { path = "actix-files" }
actix-multipart = { path = "actix-multipart" }
awc = { path = "awc" }
diff --git a/MIGRATION.md b/MIGRATION.md
index aef382a21..d2e9735fb 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -4,6 +4,14 @@
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`
+* content_length function is removed from actix-http.
+ You can set Content-Length by normally setting the response body or calling no_chunking function.
+
+* `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a
+ `u64` instead of a `usize`.
+
## 2.0.0
* `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to
@@ -358,7 +366,7 @@
* `actix_web::server` module has been removed. To start http server use `actix_web::HttpServer` type
-* StaticFiles and NamedFile has been move to separate create.
+* StaticFiles and NamedFile have been moved to a separate crate.
instead of `use actix_web::fs::StaticFile`
@@ -368,7 +376,7 @@
use `use actix_files::NamedFile`
-* Multipart has been move to separate create.
+* Multipart has been moved to a separate crate.
instead of `use actix_web::multipart::Multipart`
diff --git a/README.md b/README.md
index 0b55c5bae..97e3ceeae 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
[](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](https://docs.rs/actix-web)
[](https://crates.io/crates/actix-web)
-[](https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html)
+[](https://blog.rust-lang.org/2019/12/19/Rust-1.40.0.html)

@@ -38,6 +38,13 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
* Supports [Actix actor framework](https://github.com/actix/actix)
+* Supports Rust 1.40+
+
+## Docs
+
+* [API documentation (master)](https://actix.rs/actix-web/actix_web)
+* [API documentation (docs.rs)](https://docs.rs/actix-web)
+* [User guide](https://actix.rs)
## Example
@@ -74,7 +81,7 @@ async fn main() -> std::io::Result<()> {
* [Stateful](https://github.com/actix/examples/tree/master/state/)
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/)
-* [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
+* [Tera](https://github.com/actix/examples/tree/master/template_tera/)
* [Askama](https://github.com/actix/examples/tree/master/template_askama/) templates
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/)
diff --git a/actix-cors/CHANGES.md b/actix-cors/CHANGES.md
deleted file mode 100644
index 8022ea4e8..000000000
--- a/actix-cors/CHANGES.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Changes
-
-## [0.2.0] - 2019-12-20
-
-* Release
-
-## [0.2.0-alpha.3] - 2019-12-07
-
-* Migrate to actix-web 2.0.0
-
-* Bump `derive_more` crate version to 0.99.0
-
-## [0.1.0] - 2019-06-15
-
-* Move cors middleware to separate crate
diff --git a/actix-cors/Cargo.toml b/actix-cors/Cargo.toml
deleted file mode 100644
index 3fcd92f4f..000000000
--- a/actix-cors/Cargo.toml
+++ /dev/null
@@ -1,26 +0,0 @@
-[package]
-name = "actix-cors"
-version = "0.2.0"
-authors = ["Nikolay Kim "]
-description = "Cross-origin resource sharing (CORS) for Actix applications."
-readme = "README.md"
-keywords = ["web", "framework"]
-homepage = "https://actix.rs"
-repository = "https://github.com/actix/actix-web.git"
-documentation = "https://docs.rs/actix-cors/"
-license = "MIT/Apache-2.0"
-edition = "2018"
-workspace = ".."
-
-[lib]
-name = "actix_cors"
-path = "src/lib.rs"
-
-[dependencies]
-actix-web = "2.0.0-rc"
-actix-service = "1.0.1"
-derive_more = "0.99.2"
-futures = "0.3.1"
-
-[dev-dependencies]
-actix-rt = "1.0.0"
diff --git a/actix-cors/LICENSE-APACHE b/actix-cors/LICENSE-APACHE
deleted file mode 120000
index 965b606f3..000000000
--- a/actix-cors/LICENSE-APACHE
+++ /dev/null
@@ -1 +0,0 @@
-../LICENSE-APACHE
\ No newline at end of file
diff --git a/actix-cors/LICENSE-MIT b/actix-cors/LICENSE-MIT
deleted file mode 120000
index 76219eb72..000000000
--- a/actix-cors/LICENSE-MIT
+++ /dev/null
@@ -1 +0,0 @@
-../LICENSE-MIT
\ No newline at end of file
diff --git a/actix-cors/README.md b/actix-cors/README.md
index a77f6c6d3..c860ec5ae 100644
--- a/actix-cors/README.md
+++ b/actix-cors/README.md
@@ -1,5 +1,7 @@
# Cors Middleware for actix web framework [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-cors) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+**This crate moved to https://github.com/actix/actix-extras.**
+
## Documentation & community resources
* [User Guide](https://actix.rs/docs/)
diff --git a/actix-cors/src/lib.rs b/actix-cors/src/lib.rs
deleted file mode 100644
index 429fe9eab..000000000
--- a/actix-cors/src/lib.rs
+++ /dev/null
@@ -1,1204 +0,0 @@
-#![allow(clippy::borrow_interior_mutable_const, clippy::type_complexity)]
-//! Cross-origin resource sharing (CORS) for Actix applications
-//!
-//! CORS middleware could be used with application and with resource.
-//! Cors middleware could be used as parameter for `App::wrap()`,
-//! `Resource::wrap()` or `Scope::wrap()` methods.
-//!
-//! # Example
-//!
-//! ```rust
-//! use actix_cors::Cors;
-//! use actix_web::{http, web, App, HttpRequest, HttpResponse, HttpServer};
-//!
-//! async fn index(req: HttpRequest) -> &'static str {
-//! "Hello world"
-//! }
-//!
-//! fn main() -> std::io::Result<()> {
-//! HttpServer::new(|| App::new()
-//! .wrap(
-//! Cors::new() // <- Construct CORS middleware builder
-//! .allowed_origin("https://www.rust-lang.org/")
-//! .allowed_methods(vec!["GET", "POST"])
-//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
-//! .allowed_header(http::header::CONTENT_TYPE)
-//! .max_age(3600)
-//! .finish())
-//! .service(
-//! web::resource("/index.html")
-//! .route(web::get().to(index))
-//! .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
-//! ))
-//! .bind("127.0.0.1:8080")?;
-//!
-//! Ok(())
-//! }
-//! ```
-//! In this example custom *CORS* middleware get registered for "/index.html"
-//! endpoint.
-//!
-//! Cors middleware automatically handle *OPTIONS* preflight request.
-use std::collections::HashSet;
-use std::convert::TryFrom;
-use std::iter::FromIterator;
-use std::rc::Rc;
-use std::task::{Context, Poll};
-
-use actix_service::{Service, Transform};
-use actix_web::dev::{RequestHead, ServiceRequest, ServiceResponse};
-use actix_web::error::{Error, ResponseError, Result};
-use actix_web::http::header::{self, HeaderName, HeaderValue};
-use actix_web::http::{self, Error as HttpError, Method, StatusCode, Uri};
-use actix_web::HttpResponse;
-use derive_more::Display;
-use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready};
-
-/// A set of errors that can occur during processing CORS
-#[derive(Debug, Display)]
-pub enum CorsError {
- /// The HTTP request header `Origin` is required but was not provided
- #[display(
- fmt = "The HTTP request header `Origin` is required but was not provided"
- )]
- MissingOrigin,
- /// The HTTP request header `Origin` could not be parsed correctly.
- #[display(fmt = "The HTTP request header `Origin` could not be parsed correctly.")]
- BadOrigin,
- /// The request header `Access-Control-Request-Method` is required but is
- /// missing
- #[display(
- fmt = "The request header `Access-Control-Request-Method` is required but is missing"
- )]
- MissingRequestMethod,
- /// The request header `Access-Control-Request-Method` has an invalid value
- #[display(
- fmt = "The request header `Access-Control-Request-Method` has an invalid value"
- )]
- BadRequestMethod,
- /// The request header `Access-Control-Request-Headers` has an invalid
- /// value
- #[display(
- fmt = "The request header `Access-Control-Request-Headers` has an invalid value"
- )]
- BadRequestHeaders,
- /// Origin is not allowed to make this request
- #[display(fmt = "Origin is not allowed to make this request")]
- OriginNotAllowed,
- /// Requested method is not allowed
- #[display(fmt = "Requested method is not allowed")]
- MethodNotAllowed,
- /// One or more headers requested are not allowed
- #[display(fmt = "One or more headers requested are not allowed")]
- HeadersNotAllowed,
-}
-
-impl ResponseError for CorsError {
- fn status_code(&self) -> StatusCode {
- StatusCode::BAD_REQUEST
- }
-
- fn error_response(&self) -> HttpResponse {
- HttpResponse::with_body(StatusCode::BAD_REQUEST, format!("{}", self).into())
- }
-}
-
-/// An enum signifying that some of type T is allowed, or `All` (everything is
-/// allowed).
-///
-/// `Default` is implemented for this enum and is `All`.
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum AllOrSome {
- /// Everything is allowed. Usually equivalent to the "*" value.
- All,
- /// Only some of `T` is allowed
- Some(T),
-}
-
-impl Default for AllOrSome {
- fn default() -> Self {
- AllOrSome::All
- }
-}
-
-impl AllOrSome {
- /// Returns whether this is an `All` variant
- pub fn is_all(&self) -> bool {
- match *self {
- AllOrSome::All => true,
- AllOrSome::Some(_) => false,
- }
- }
-
- /// Returns whether this is a `Some` variant
- pub fn is_some(&self) -> bool {
- !self.is_all()
- }
-
- /// Returns &T
- pub fn as_ref(&self) -> Option<&T> {
- match *self {
- AllOrSome::All => None,
- AllOrSome::Some(ref t) => Some(t),
- }
- }
-}
-
-/// Structure that follows the builder pattern for building `Cors` middleware
-/// structs.
-///
-/// To construct a cors:
-///
-/// 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
-/// 2. Use any of the builder methods to set fields in the backend.
-/// 3. Call [finish](struct.Cors.html#method.finish) to retrieve the
-/// constructed backend.
-///
-/// # Example
-///
-/// ```rust
-/// use actix_cors::Cors;
-/// use actix_web::http::header;
-///
-/// # fn main() {
-/// let cors = Cors::new()
-/// .allowed_origin("https://www.rust-lang.org/")
-/// .allowed_methods(vec!["GET", "POST"])
-/// .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
-/// .allowed_header(header::CONTENT_TYPE)
-/// .max_age(3600);
-/// # }
-/// ```
-#[derive(Default)]
-pub struct Cors {
- cors: Option,
- methods: bool,
- error: Option,
- expose_hdrs: HashSet,
-}
-
-impl Cors {
- /// Build a new CORS middleware instance
- pub fn new() -> Cors {
- Cors {
- cors: Some(Inner {
- origins: AllOrSome::All,
- origins_str: None,
- methods: HashSet::new(),
- headers: AllOrSome::All,
- expose_hdrs: None,
- max_age: None,
- preflight: true,
- send_wildcard: false,
- supports_credentials: false,
- vary_header: true,
- }),
- methods: false,
- error: None,
- expose_hdrs: HashSet::new(),
- }
- }
-
- /// Build a new CORS default middleware
- pub fn default() -> CorsFactory {
- let inner = Inner {
- origins: AllOrSome::default(),
- origins_str: None,
- methods: HashSet::from_iter(
- vec![
- Method::GET,
- Method::HEAD,
- Method::POST,
- Method::OPTIONS,
- Method::PUT,
- Method::PATCH,
- Method::DELETE,
- ]
- .into_iter(),
- ),
- headers: AllOrSome::All,
- expose_hdrs: None,
- max_age: None,
- preflight: true,
- send_wildcard: false,
- supports_credentials: false,
- vary_header: true,
- };
- CorsFactory {
- inner: Rc::new(inner),
- }
- }
-
- /// Add an origin that are allowed to make requests.
- /// Will be verified against the `Origin` request header.
- ///
- /// When `All` is set, and `send_wildcard` is set, "*" will be sent in
- /// the `Access-Control-Allow-Origin` response header. Otherwise, the
- /// client's `Origin` request header will be echoed back in the
- /// `Access-Control-Allow-Origin` response header.
- ///
- /// When `Some` is set, the client's `Origin` request header will be
- /// checked in a case-sensitive manner.
- ///
- /// This is the `list of origins` in the
- /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
- ///
- /// Defaults to `All`.
- ///
- /// Builder panics if supplied origin is not valid uri.
- pub fn allowed_origin(mut self, origin: &str) -> Cors {
- if let Some(cors) = cors(&mut self.cors, &self.error) {
- match Uri::try_from(origin) {
- Ok(_) => {
- if cors.origins.is_all() {
- cors.origins = AllOrSome::Some(HashSet::new());
- }
- if let AllOrSome::Some(ref mut origins) = cors.origins {
- origins.insert(origin.to_owned());
- }
- }
- Err(e) => {
- self.error = Some(e.into());
- }
- }
- }
- self
- }
-
- /// Set a list of methods which the allowed origins are allowed to access
- /// for requests.
- ///
- /// This is the `list of methods` in the
- /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
- ///
- /// Defaults to `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]`
- pub fn allowed_methods(mut self, methods: U) -> Cors
- where
- U: IntoIterator- ,
- Method: TryFrom,
- >::Error: Into,
- {
- self.methods = true;
- if let Some(cors) = cors(&mut self.cors, &self.error) {
- for m in methods {
- match Method::try_from(m) {
- Ok(method) => {
- cors.methods.insert(method);
- }
- Err(e) => {
- self.error = Some(e.into());
- break;
- }
- }
- }
- }
- self
- }
-
- /// Set an allowed header
- pub fn allowed_header(mut self, header: H) -> Cors
- where
- HeaderName: TryFrom,
- >::Error: Into,
- {
- if let Some(cors) = cors(&mut self.cors, &self.error) {
- match HeaderName::try_from(header) {
- Ok(method) => {
- if cors.headers.is_all() {
- cors.headers = AllOrSome::Some(HashSet::new());
- }
- if let AllOrSome::Some(ref mut headers) = cors.headers {
- headers.insert(method);
- }
- }
- Err(e) => self.error = Some(e.into()),
- }
- }
- self
- }
-
- /// Set a list of header field names which can be used when
- /// this resource is accessed by allowed origins.
- ///
- /// If `All` is set, whatever is requested by the client in
- /// `Access-Control-Request-Headers` will be echoed back in the
- /// `Access-Control-Allow-Headers` header.
- ///
- /// This is the `list of headers` in the
- /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
- ///
- /// Defaults to `All`.
- pub fn allowed_headers(mut self, headers: U) -> Cors
- where
- U: IntoIterator
- ,
- HeaderName: TryFrom,
- >::Error: Into,
- {
- if let Some(cors) = cors(&mut self.cors, &self.error) {
- for h in headers {
- match HeaderName::try_from(h) {
- Ok(method) => {
- if cors.headers.is_all() {
- cors.headers = AllOrSome::Some(HashSet::new());
- }
- if let AllOrSome::Some(ref mut headers) = cors.headers {
- headers.insert(method);
- }
- }
- Err(e) => {
- self.error = Some(e.into());
- break;
- }
- }
- }
- }
- self
- }
-
- /// Set a list of headers which are safe to expose to the API of a CORS API
- /// specification. This corresponds to the
- /// `Access-Control-Expose-Headers` response header.
- ///
- /// This is the `list of exposed headers` in the
- /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
- ///
- /// This defaults to an empty set.
- pub fn expose_headers(mut self, headers: U) -> Cors
- where
- U: IntoIterator
- ,
- HeaderName: TryFrom,
- >::Error: Into,
- {
- for h in headers {
- match HeaderName::try_from(h) {
- Ok(method) => {
- self.expose_hdrs.insert(method);
- }
- Err(e) => {
- self.error = Some(e.into());
- break;
- }
- }
- }
- self
- }
-
- /// Set a maximum time for which this CORS request maybe cached.
- /// This value is set as the `Access-Control-Max-Age` header.
- ///
- /// This defaults to `None` (unset).
- pub fn max_age(mut self, max_age: usize) -> Cors {
- if let Some(cors) = cors(&mut self.cors, &self.error) {
- cors.max_age = Some(max_age)
- }
- self
- }
-
- /// Set a wildcard origins
- ///
- /// If send wildcard is set and the `allowed_origins` parameter is `All`, a
- /// wildcard `Access-Control-Allow-Origin` response header is sent,
- /// rather than the request’s `Origin` header.
- ///
- /// This is the `supports credentials flag` in the
- /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
- ///
- /// This **CANNOT** be used in conjunction with `allowed_origins` set to
- /// `All` and `allow_credentials` set to `true`. Depending on the mode
- /// of usage, this will either result in an `Error::
- /// CredentialsWithWildcardOrigin` error during actix launch or runtime.
- ///
- /// Defaults to `false`.
- pub fn send_wildcard(mut self) -> Cors {
- if let Some(cors) = cors(&mut self.cors, &self.error) {
- cors.send_wildcard = true
- }
- self
- }
-
- /// Allows users to make authenticated requests
- ///
- /// If true, injects the `Access-Control-Allow-Credentials` header in
- /// responses. This allows cookies and credentials to be submitted
- /// across domains.
- ///
- /// This option cannot be used in conjunction with an `allowed_origin` set
- /// to `All` and `send_wildcards` set to `true`.
- ///
- /// Defaults to `false`.
- ///
- /// Builder panics if credentials are allowed, but the Origin is set to "*".
- /// This is not allowed by W3C
- pub fn supports_credentials(mut self) -> Cors {
- if let Some(cors) = cors(&mut self.cors, &self.error) {
- cors.supports_credentials = true
- }
- self
- }
-
- /// Disable `Vary` header support.
- ///
- /// When enabled the header `Vary: Origin` will be returned as per the W3
- /// implementation guidelines.
- ///
- /// Setting this header when the `Access-Control-Allow-Origin` is
- /// dynamically generated (e.g. when there is more than one allowed
- /// origin, and an Origin than '*' is returned) informs CDNs and other
- /// caches that the CORS headers are dynamic, and cannot be cached.
- ///
- /// By default `vary` header support is enabled.
- pub fn disable_vary_header(mut self) -> Cors {
- if let Some(cors) = cors(&mut self.cors, &self.error) {
- cors.vary_header = false
- }
- self
- }
-
- /// Disable *preflight* request support.
- ///
- /// When enabled cors middleware automatically handles *OPTIONS* request.
- /// This is useful application level middleware.
- ///
- /// By default *preflight* support is enabled.
- pub fn disable_preflight(mut self) -> Cors {
- if let Some(cors) = cors(&mut self.cors, &self.error) {
- cors.preflight = false
- }
- self
- }
-
- /// Construct cors middleware
- pub fn finish(self) -> CorsFactory {
- let mut slf = if !self.methods {
- self.allowed_methods(vec![
- Method::GET,
- Method::HEAD,
- Method::POST,
- Method::OPTIONS,
- Method::PUT,
- Method::PATCH,
- Method::DELETE,
- ])
- } else {
- self
- };
-
- if let Some(e) = slf.error.take() {
- panic!("{}", e);
- }
-
- let mut cors = slf.cors.take().expect("cannot reuse CorsBuilder");
-
- if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() {
- panic!("Credentials are allowed, but the Origin is set to \"*\"");
- }
-
- if let AllOrSome::Some(ref origins) = cors.origins {
- let s = origins
- .iter()
- .fold(String::new(), |s, v| format!("{}, {}", s, v));
- cors.origins_str = Some(HeaderValue::try_from(&s[2..]).unwrap());
- }
-
- if !slf.expose_hdrs.is_empty() {
- cors.expose_hdrs = Some(
- slf.expose_hdrs
- .iter()
- .fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..]
- .to_owned(),
- );
- }
-
- CorsFactory {
- inner: Rc::new(cors),
- }
- }
-}
-
-fn cors<'a>(
- parts: &'a mut Option,
- err: &Option,
-) -> Option<&'a mut Inner> {
- if err.is_some() {
- return None;
- }
- parts.as_mut()
-}
-
-/// `Middleware` for Cross-origin resource sharing support
-///
-/// The Cors struct contains the settings for CORS requests to be validated and
-/// for responses to be generated.
-pub struct CorsFactory {
- inner: Rc,
-}
-
-impl
Transform for CorsFactory
-where
- S: Service, Error = Error>,
- S::Future: 'static,
- B: 'static,
-{
- type Request = ServiceRequest;
- type Response = ServiceResponse;
- type Error = Error;
- type InitError = ();
- type Transform = CorsMiddleware;
- type Future = Ready>;
-
- fn new_transform(&self, service: S) -> Self::Future {
- ok(CorsMiddleware {
- service,
- inner: self.inner.clone(),
- })
- }
-}
-
-/// `Middleware` for Cross-origin resource sharing support
-///
-/// The Cors struct contains the settings for CORS requests to be validated and
-/// for responses to be generated.
-#[derive(Clone)]
-pub struct CorsMiddleware {
- service: S,
- inner: Rc,
-}
-
-struct Inner {
- methods: HashSet,
- origins: AllOrSome>,
- origins_str: Option,
- headers: AllOrSome>,
- expose_hdrs: Option,
- max_age: Option,
- preflight: bool,
- send_wildcard: bool,
- supports_credentials: bool,
- vary_header: bool,
-}
-
-impl Inner {
- fn validate_origin(&self, req: &RequestHead) -> Result<(), CorsError> {
- if let Some(hdr) = req.headers().get(&header::ORIGIN) {
- if let Ok(origin) = hdr.to_str() {
- return match self.origins {
- AllOrSome::All => Ok(()),
- AllOrSome::Some(ref allowed_origins) => allowed_origins
- .get(origin)
- .and_then(|_| Some(()))
- .ok_or_else(|| CorsError::OriginNotAllowed),
- };
- }
- Err(CorsError::BadOrigin)
- } else {
- match self.origins {
- AllOrSome::All => Ok(()),
- _ => Err(CorsError::MissingOrigin),
- }
- }
- }
-
- fn access_control_allow_origin(&self, req: &RequestHead) -> Option {
- match self.origins {
- AllOrSome::All => {
- if self.send_wildcard {
- Some(HeaderValue::from_static("*"))
- } else if let Some(origin) = req.headers().get(&header::ORIGIN) {
- Some(origin.clone())
- } else {
- None
- }
- }
- AllOrSome::Some(ref origins) => {
- if let Some(origin) =
- req.headers()
- .get(&header::ORIGIN)
- .filter(|o| match o.to_str() {
- Ok(os) => origins.contains(os),
- _ => false,
- })
- {
- Some(origin.clone())
- } else {
- Some(self.origins_str.as_ref().unwrap().clone())
- }
- }
- }
- }
-
- fn validate_allowed_method(&self, req: &RequestHead) -> Result<(), CorsError> {
- if let Some(hdr) = req.headers().get(&header::ACCESS_CONTROL_REQUEST_METHOD) {
- if let Ok(meth) = hdr.to_str() {
- if let Ok(method) = Method::try_from(meth) {
- return self
- .methods
- .get(&method)
- .and_then(|_| Some(()))
- .ok_or_else(|| CorsError::MethodNotAllowed);
- }
- }
- Err(CorsError::BadRequestMethod)
- } else {
- Err(CorsError::MissingRequestMethod)
- }
- }
-
- fn validate_allowed_headers(&self, req: &RequestHead) -> Result<(), CorsError> {
- match self.headers {
- AllOrSome::All => Ok(()),
- AllOrSome::Some(ref allowed_headers) => {
- if let Some(hdr) =
- req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS)
- {
- if let Ok(headers) = hdr.to_str() {
- let mut hdrs = HashSet::new();
- for hdr in headers.split(',') {
- match HeaderName::try_from(hdr.trim()) {
- Ok(hdr) => hdrs.insert(hdr),
- Err(_) => return Err(CorsError::BadRequestHeaders),
- };
- }
- // `Access-Control-Request-Headers` must contain 1 or more
- // `field-name`.
- if !hdrs.is_empty() {
- if !hdrs.is_subset(allowed_headers) {
- return Err(CorsError::HeadersNotAllowed);
- }
- return Ok(());
- }
- }
- Err(CorsError::BadRequestHeaders)
- } else {
- Ok(())
- }
- }
- }
- }
-}
-
-impl Service for CorsMiddleware
-where
- S: Service, Error = Error>,
- S::Future: 'static,
- B: 'static,
-{
- type Request = ServiceRequest;
- type Response = ServiceResponse;
- type Error = Error;
- type Future = Either<
- Ready>,
- LocalBoxFuture<'static, Result>,
- >;
-
- fn poll_ready(&mut self, cx: &mut Context) -> Poll> {
- self.service.poll_ready(cx)
- }
-
- fn call(&mut self, req: ServiceRequest) -> Self::Future {
- if self.inner.preflight && Method::OPTIONS == *req.method() {
- if let Err(e) = self
- .inner
- .validate_origin(req.head())
- .and_then(|_| self.inner.validate_allowed_method(req.head()))
- .and_then(|_| self.inner.validate_allowed_headers(req.head()))
- {
- return Either::Left(ok(req.error_response(e)));
- }
-
- // allowed headers
- let headers = if let Some(headers) = self.inner.headers.as_ref() {
- Some(
- HeaderValue::try_from(
- &headers
- .iter()
- .fold(String::new(), |s, v| s + "," + v.as_str())
- .as_str()[1..],
- )
- .unwrap(),
- )
- } else if let Some(hdr) =
- req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS)
- {
- Some(hdr.clone())
- } else {
- None
- };
-
- let res = HttpResponse::Ok()
- .if_some(self.inner.max_age.as_ref(), |max_age, resp| {
- let _ = resp.header(
- header::ACCESS_CONTROL_MAX_AGE,
- format!("{}", max_age).as_str(),
- );
- })
- .if_some(headers, |headers, resp| {
- let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers);
- })
- .if_some(
- self.inner.access_control_allow_origin(req.head()),
- |origin, resp| {
- let _ = resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
- },
- )
- .if_true(self.inner.supports_credentials, |resp| {
- resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
- })
- .header(
- header::ACCESS_CONTROL_ALLOW_METHODS,
- &self
- .inner
- .methods
- .iter()
- .fold(String::new(), |s, v| s + "," + v.as_str())
- .as_str()[1..],
- )
- .finish()
- .into_body();
-
- Either::Left(ok(req.into_response(res)))
- } else {
- if req.headers().contains_key(&header::ORIGIN) {
- // Only check requests with a origin header.
- if let Err(e) = self.inner.validate_origin(req.head()) {
- return Either::Left(ok(req.error_response(e)));
- }
- }
-
- let inner = self.inner.clone();
- let has_origin = req.headers().contains_key(&header::ORIGIN);
- let fut = self.service.call(req);
-
- Either::Right(
- async move {
- let res = fut.await;
-
- if has_origin {
- let mut res = res?;
- if let Some(origin) =
- inner.access_control_allow_origin(res.request().head())
- {
- res.headers_mut().insert(
- header::ACCESS_CONTROL_ALLOW_ORIGIN,
- origin.clone(),
- );
- };
-
- if let Some(ref expose) = inner.expose_hdrs {
- res.headers_mut().insert(
- header::ACCESS_CONTROL_EXPOSE_HEADERS,
- HeaderValue::try_from(expose.as_str()).unwrap(),
- );
- }
- if inner.supports_credentials {
- res.headers_mut().insert(
- header::ACCESS_CONTROL_ALLOW_CREDENTIALS,
- HeaderValue::from_static("true"),
- );
- }
- if inner.vary_header {
- let value = if let Some(hdr) =
- res.headers_mut().get(&header::VARY)
- {
- let mut val: Vec =
- Vec::with_capacity(hdr.as_bytes().len() + 8);
- val.extend(hdr.as_bytes());
- val.extend(b", Origin");
- HeaderValue::try_from(&val[..]).unwrap()
- } else {
- HeaderValue::from_static("Origin")
- };
- res.headers_mut().insert(header::VARY, value);
- }
- Ok(res)
- } else {
- res
- }
- }
- .boxed_local(),
- )
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use actix_service::{fn_service, Transform};
- use actix_web::test::{self, TestRequest};
-
- use super::*;
-
- #[actix_rt::test]
- #[should_panic(expected = "Credentials are allowed, but the Origin is set to")]
- async fn cors_validates_illegal_allow_credentials() {
- let _cors = Cors::new().supports_credentials().send_wildcard().finish();
- }
-
- #[actix_rt::test]
- async fn validate_origin_allows_all_origins() {
- let mut cors = Cors::new()
- .finish()
- .new_transform(test::ok_service())
- .await
- .unwrap();
- let req = TestRequest::with_header("Origin", "https://www.example.com")
- .to_srv_request();
-
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(resp.status(), StatusCode::OK);
- }
-
- #[actix_rt::test]
- async fn default() {
- let mut cors = Cors::default()
- .new_transform(test::ok_service())
- .await
- .unwrap();
- let req = TestRequest::with_header("Origin", "https://www.example.com")
- .to_srv_request();
-
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(resp.status(), StatusCode::OK);
- }
-
- #[actix_rt::test]
- async fn test_preflight() {
- let mut cors = Cors::new()
- .send_wildcard()
- .max_age(3600)
- .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
- .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
- .allowed_header(header::CONTENT_TYPE)
- .finish()
- .new_transform(test::ok_service())
- .await
- .unwrap();
-
- let req = TestRequest::with_header("Origin", "https://www.example.com")
- .method(Method::OPTIONS)
- .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "X-Not-Allowed")
- .to_srv_request();
-
- assert!(cors.inner.validate_allowed_method(req.head()).is_err());
- assert!(cors.inner.validate_allowed_headers(req.head()).is_err());
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
-
- let req = TestRequest::with_header("Origin", "https://www.example.com")
- .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put")
- .method(Method::OPTIONS)
- .to_srv_request();
-
- assert!(cors.inner.validate_allowed_method(req.head()).is_err());
- assert!(cors.inner.validate_allowed_headers(req.head()).is_ok());
-
- let req = TestRequest::with_header("Origin", "https://www.example.com")
- .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
- .header(
- header::ACCESS_CONTROL_REQUEST_HEADERS,
- "AUTHORIZATION,ACCEPT",
- )
- .method(Method::OPTIONS)
- .to_srv_request();
-
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(
- &b"*"[..],
- resp.headers()
- .get(&header::ACCESS_CONTROL_ALLOW_ORIGIN)
- .unwrap()
- .as_bytes()
- );
- assert_eq!(
- &b"3600"[..],
- resp.headers()
- .get(&header::ACCESS_CONTROL_MAX_AGE)
- .unwrap()
- .as_bytes()
- );
- let hdr = resp
- .headers()
- .get(&header::ACCESS_CONTROL_ALLOW_HEADERS)
- .unwrap()
- .to_str()
- .unwrap();
- assert!(hdr.contains("authorization"));
- assert!(hdr.contains("accept"));
- assert!(hdr.contains("content-type"));
-
- let methods = resp
- .headers()
- .get(header::ACCESS_CONTROL_ALLOW_METHODS)
- .unwrap()
- .to_str()
- .unwrap();
- assert!(methods.contains("POST"));
- assert!(methods.contains("GET"));
- assert!(methods.contains("OPTIONS"));
-
- Rc::get_mut(&mut cors.inner).unwrap().preflight = false;
-
- let req = TestRequest::with_header("Origin", "https://www.example.com")
- .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
- .header(
- header::ACCESS_CONTROL_REQUEST_HEADERS,
- "AUTHORIZATION,ACCEPT",
- )
- .method(Method::OPTIONS)
- .to_srv_request();
-
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(resp.status(), StatusCode::OK);
- }
-
- // #[actix_rt::test]
- // #[should_panic(expected = "MissingOrigin")]
- // async fn test_validate_missing_origin() {
- // let cors = Cors::build()
- // .allowed_origin("https://www.example.com")
- // .finish();
- // let mut req = HttpRequest::default();
- // cors.start(&req).unwrap();
- // }
-
- #[actix_rt::test]
- #[should_panic(expected = "OriginNotAllowed")]
- async fn test_validate_not_allowed_origin() {
- let cors = Cors::new()
- .allowed_origin("https://www.example.com")
- .finish()
- .new_transform(test::ok_service())
- .await
- .unwrap();
-
- let req = TestRequest::with_header("Origin", "https://www.unknown.com")
- .method(Method::GET)
- .to_srv_request();
- cors.inner.validate_origin(req.head()).unwrap();
- cors.inner.validate_allowed_method(req.head()).unwrap();
- cors.inner.validate_allowed_headers(req.head()).unwrap();
- }
-
- #[actix_rt::test]
- async fn test_validate_origin() {
- let mut cors = Cors::new()
- .allowed_origin("https://www.example.com")
- .finish()
- .new_transform(test::ok_service())
- .await
- .unwrap();
-
- let req = TestRequest::with_header("Origin", "https://www.example.com")
- .method(Method::GET)
- .to_srv_request();
-
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(resp.status(), StatusCode::OK);
- }
-
- #[actix_rt::test]
- async fn test_no_origin_response() {
- let mut cors = Cors::new()
- .disable_preflight()
- .finish()
- .new_transform(test::ok_service())
- .await
- .unwrap();
-
- let req = TestRequest::default().method(Method::GET).to_srv_request();
- let resp = test::call_service(&mut cors, req).await;
- assert!(resp
- .headers()
- .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
- .is_none());
-
- let req = TestRequest::with_header("Origin", "https://www.example.com")
- .method(Method::OPTIONS)
- .to_srv_request();
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(
- &b"https://www.example.com"[..],
- resp.headers()
- .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
- .unwrap()
- .as_bytes()
- );
- }
-
- #[actix_rt::test]
- async fn test_response() {
- let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
- let mut cors = Cors::new()
- .send_wildcard()
- .disable_preflight()
- .max_age(3600)
- .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
- .allowed_headers(exposed_headers.clone())
- .expose_headers(exposed_headers.clone())
- .allowed_header(header::CONTENT_TYPE)
- .finish()
- .new_transform(test::ok_service())
- .await
- .unwrap();
-
- let req = TestRequest::with_header("Origin", "https://www.example.com")
- .method(Method::OPTIONS)
- .to_srv_request();
-
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(
- &b"*"[..],
- resp.headers()
- .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
- .unwrap()
- .as_bytes()
- );
- assert_eq!(
- &b"Origin"[..],
- resp.headers().get(header::VARY).unwrap().as_bytes()
- );
-
- {
- let headers = resp
- .headers()
- .get(header::ACCESS_CONTROL_EXPOSE_HEADERS)
- .unwrap()
- .to_str()
- .unwrap()
- .split(',')
- .map(|s| s.trim())
- .collect::>();
-
- for h in exposed_headers {
- assert!(headers.contains(&h.as_str()));
- }
- }
-
- let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
- let mut cors = Cors::new()
- .send_wildcard()
- .disable_preflight()
- .max_age(3600)
- .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
- .allowed_headers(exposed_headers.clone())
- .expose_headers(exposed_headers.clone())
- .allowed_header(header::CONTENT_TYPE)
- .finish()
- .new_transform(fn_service(|req: ServiceRequest| {
- ok(req.into_response(
- HttpResponse::Ok().header(header::VARY, "Accept").finish(),
- ))
- }))
- .await
- .unwrap();
- let req = TestRequest::with_header("Origin", "https://www.example.com")
- .method(Method::OPTIONS)
- .to_srv_request();
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(
- &b"Accept, Origin"[..],
- resp.headers().get(header::VARY).unwrap().as_bytes()
- );
-
- let mut cors = Cors::new()
- .disable_vary_header()
- .allowed_origin("https://www.example.com")
- .allowed_origin("https://www.google.com")
- .finish()
- .new_transform(test::ok_service())
- .await
- .unwrap();
-
- let req = TestRequest::with_header("Origin", "https://www.example.com")
- .method(Method::OPTIONS)
- .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
- .to_srv_request();
- let resp = test::call_service(&mut cors, req).await;
-
- let origins_str = resp
- .headers()
- .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
- .unwrap()
- .to_str()
- .unwrap();
-
- assert_eq!("https://www.example.com", origins_str);
- }
-
- #[actix_rt::test]
- async fn test_multiple_origins() {
- let mut cors = Cors::new()
- .allowed_origin("https://example.com")
- .allowed_origin("https://example.org")
- .allowed_methods(vec![Method::GET])
- .finish()
- .new_transform(test::ok_service())
- .await
- .unwrap();
-
- let req = TestRequest::with_header("Origin", "https://example.com")
- .method(Method::GET)
- .to_srv_request();
-
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(
- &b"https://example.com"[..],
- resp.headers()
- .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
- .unwrap()
- .as_bytes()
- );
-
- let req = TestRequest::with_header("Origin", "https://example.org")
- .method(Method::GET)
- .to_srv_request();
-
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(
- &b"https://example.org"[..],
- resp.headers()
- .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
- .unwrap()
- .as_bytes()
- );
- }
-
- #[actix_rt::test]
- async fn test_multiple_origins_preflight() {
- let mut cors = Cors::new()
- .allowed_origin("https://example.com")
- .allowed_origin("https://example.org")
- .allowed_methods(vec![Method::GET])
- .finish()
- .new_transform(test::ok_service())
- .await
- .unwrap();
-
- let req = TestRequest::with_header("Origin", "https://example.com")
- .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
- .method(Method::OPTIONS)
- .to_srv_request();
-
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(
- &b"https://example.com"[..],
- resp.headers()
- .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
- .unwrap()
- .as_bytes()
- );
-
- let req = TestRequest::with_header("Origin", "https://example.org")
- .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
- .method(Method::OPTIONS)
- .to_srv_request();
-
- let resp = test::call_service(&mut cors, req).await;
- assert_eq!(
- &b"https://example.org"[..],
- resp.headers()
- .get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
- .unwrap()
- .as_bytes()
- );
- }
-}
diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md
index c4918b56d..abf143997 100644
--- a/actix-files/CHANGES.md
+++ b/actix-files/CHANGES.md
@@ -1,5 +1,14 @@
# Changes
+## [0.3.0-alpha.1] - 2020-05-23
+
+* Update `actix-web` and `actix-http` dependencies to alpha
+* Fix some typos in the docs
+* Bump minimum supported Rust version to 1.40
+* Support sending Content-Length when Content-Range is specified [#1384]
+
+[#1384]: https://github.com/actix/actix-web/pull/1384
+
## [0.2.1] - 2019-12-22
* Use the same format for file URLs regardless of platforms
diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml
index 104eb3dfa..356c7a413 100644
--- a/actix-files/Cargo.toml
+++ b/actix-files/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "actix-files"
-version = "0.2.1"
+version = "0.3.0-alpha.1"
authors = ["Nikolay Kim "]
description = "Static files support for actix web."
readme = "README.md"
@@ -18,12 +18,13 @@ name = "actix_files"
path = "src/lib.rs"
[dependencies]
-actix-web = { version = "2.0.0-rc", default-features = false }
-actix-http = "1.0.1"
+actix-web = { version = "3.0.0-alpha.3", default-features = false }
+actix-http = "2.0.0-alpha.4"
actix-service = "1.0.1"
bitflags = "1"
bytes = "0.5.3"
-futures = "0.3.1"
+futures-core = { version = "0.3.5", default-features = false }
+futures-util = { version = "0.3.5", default-features = false }
derive_more = "0.99.2"
log = "0.4"
mime = "0.3"
@@ -33,4 +34,4 @@ v_htmlescape = "0.4"
[dev-dependencies]
actix-rt = "1.0.0"
-actix-web = { version = "2.0.0-rc", features=["openssl"] }
+actix-web = { version = "3.0.0-alpha.3", features = ["openssl"] }
diff --git a/actix-files/README.md b/actix-files/README.md
index 9585e67a8..5a5a62083 100644
--- a/actix-files/README.md
+++ b/actix-files/README.md
@@ -6,4 +6,4 @@
* [API Documentation](https://docs.rs/actix-files/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-files](https://crates.io/crates/actix-files)
-* Minimum supported Rust version: 1.33 or later
+* Minimum supported Rust version: 1.40 or later
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-files/src/lib.rs b/actix-files/src/lib.rs
index d910b7d5f..76c68ce25 100644
--- a/actix-files/src/lib.rs
+++ b/actix-files/src/lib.rs
@@ -24,8 +24,8 @@ use actix_web::http::header::{self, DispositionType};
use actix_web::http::Method;
use actix_web::{web, FromRequest, HttpRequest, HttpResponse};
use bytes::Bytes;
-use futures::future::{ok, ready, Either, FutureExt, LocalBoxFuture, Ready};
-use futures::Stream;
+use futures_core::Stream;
+use futures_util::future::{ok, ready, Either, FutureExt, LocalBoxFuture, Ready};
use mime;
use mime_guess::from_ext;
use percent_encoding::{utf8_percent_encode, CONTROLS};
@@ -521,7 +521,7 @@ impl Service for FilesService {
Err(e) => return Either::Left(ok(req.error_response(e))),
};
- // full filepath
+ // full file path
let path = match self.directory.join(&real_path.0).canonicalize() {
Ok(path) => path,
Err(e) => return self.handle_err(e, req),
@@ -952,135 +952,92 @@ mod tests {
#[actix_rt::test]
async fn test_named_file_content_range_headers() {
- let mut srv = test::init_service(
- App::new().service(Files::new("/test", ".").index_file("tests/test.binary")),
- )
- .await;
+ let srv = test::start(|| {
+ App::new().service(Files::new("/", "."))
+ });
// Valid range header
- let request = TestRequest::get()
- .uri("/t%65st/tests/test.binary")
+ let response = srv
+ .get("/tests/test.binary")
.header(header::RANGE, "bytes=10-20")
- .to_request();
-
- let response = test::call_service(&mut srv, request).await;
- let contentrange = response
- .headers()
- .get(header::CONTENT_RANGE)
- .unwrap()
- .to_str()
+ .send()
+ .await
.unwrap();
-
- assert_eq!(contentrange, "bytes 10-20/100");
+ let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
+ assert_eq!(content_range.to_str().unwrap(), "bytes 10-20/100");
// Invalid range header
- let request = TestRequest::get()
- .uri("/t%65st/tests/test.binary")
+ let response = srv
+ .get("/tests/test.binary")
.header(header::RANGE, "bytes=10-5")
- .to_request();
- let response = test::call_service(&mut srv, request).await;
-
- let contentrange = response
- .headers()
- .get(header::CONTENT_RANGE)
- .unwrap()
- .to_str()
+ .send()
+ .await
.unwrap();
-
- assert_eq!(contentrange, "bytes */100");
+ let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
+ assert_eq!(content_range.to_str().unwrap(), "bytes */100");
}
#[actix_rt::test]
async fn test_named_file_content_length_headers() {
- // use actix_web::body::{MessageBody, ResponseBody};
-
- let mut srv = test::init_service(
- App::new().service(Files::new("test", ".").index_file("tests/test.binary")),
- )
- .await;
+ let srv = test::start(|| {
+ App::new().service(Files::new("/", "."))
+ });
// Valid range header
- let request = TestRequest::get()
- .uri("/t%65st/tests/test.binary")
+ let response = srv
+ .get("/tests/test.binary")
.header(header::RANGE, "bytes=10-20")
- .to_request();
- let _response = test::call_service(&mut srv, request).await;
+ .send()
+ .await
+ .unwrap();
+ let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
+ assert_eq!(content_length.to_str().unwrap(), "11");
- // let contentlength = response
- // .headers()
- // .get(header::CONTENT_LENGTH)
- // .unwrap()
- // .to_str()
- // .unwrap();
- // assert_eq!(contentlength, "11");
-
- // Invalid range header
- let request = TestRequest::get()
- .uri("/t%65st/tests/test.binary")
- .header(header::RANGE, "bytes=10-8")
- .to_request();
- let response = test::call_service(&mut srv, request).await;
- assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
+ // Valid range header, starting from 0
+ let response = srv
+ .get("/tests/test.binary")
+ .header(header::RANGE, "bytes=0-20")
+ .send()
+ .await
+ .unwrap();
+ let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
+ assert_eq!(content_length.to_str().unwrap(), "21");
// Without range header
- let request = TestRequest::get()
- .uri("/t%65st/tests/test.binary")
- // .no_default_headers()
- .to_request();
- let _response = test::call_service(&mut srv, request).await;
+ let mut response = srv.get("/tests/test.binary").send().await.unwrap();
+ let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
+ assert_eq!(content_length.to_str().unwrap(), "100");
- // let contentlength = response
- // .headers()
- // .get(header::CONTENT_LENGTH)
- // .unwrap()
- // .to_str()
- // .unwrap();
- // assert_eq!(contentlength, "100");
+ // Should be no transfer-encoding
+ let transfer_encoding = response.headers().get(header::TRANSFER_ENCODING);
+ assert!(transfer_encoding.is_none());
- // chunked
- let request = TestRequest::get()
- .uri("/t%65st/tests/test.binary")
- .to_request();
- let response = test::call_service(&mut srv, request).await;
-
- // with enabled compression
- // {
- // let te = response
- // .headers()
- // .get(header::TRANSFER_ENCODING)
- // .unwrap()
- // .to_str()
- // .unwrap();
- // assert_eq!(te, "chunked");
- // }
-
- let bytes = test::read_body(response).await;
+ // Check file contents
+ let bytes = response.body().await.unwrap();
let data = Bytes::from(fs::read("tests/test.binary").unwrap());
assert_eq!(bytes, data);
}
#[actix_rt::test]
async fn test_head_content_length_headers() {
- let mut srv = test::init_service(
- App::new().service(Files::new("test", ".").index_file("tests/test.binary")),
- )
- .await;
+ let srv = test::start(|| {
+ App::new().service(Files::new("/", "."))
+ });
- // Valid range header
- let request = TestRequest::default()
- .method(Method::HEAD)
- .uri("/t%65st/tests/test.binary")
- .to_request();
- let _response = test::call_service(&mut srv, request).await;
+ let response = srv
+ .head("/tests/test.binary")
+ .send()
+ .await
+ .unwrap();
- // TODO: fix check
- // let contentlength = response
- // .headers()
- // .get(header::CONTENT_LENGTH)
- // .unwrap()
- // .to_str()
- // .unwrap();
- // assert_eq!(contentlength, "100");
+ let content_length = response
+ .headers()
+ .get(header::CONTENT_LENGTH)
+ .unwrap()
+ .to_str()
+ .unwrap();
+
+ assert_eq!(content_length, "100");
}
#[actix_rt::test]
diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs
index fdb055998..6ee561a4b 100644
--- a/actix-files/src/named.rs
+++ b/actix-files/src/named.rs
@@ -18,7 +18,7 @@ use actix_web::http::header::{
};
use actix_web::http::{ContentEncoding, StatusCode};
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder};
-use futures::future::{ready, Ready};
+use futures_util::future::{ready, Ready};
use crate::range::HttpRange;
use crate::ChunkedReadFile;
@@ -388,11 +388,12 @@ impl NamedFile {
fut: None,
counter: 0,
};
+
if offset != 0 || length != self.md.len() {
- Ok(resp.status(StatusCode::PARTIAL_CONTENT).streaming(reader))
- } else {
- Ok(resp.body(SizedStream::new(length, reader)))
+ resp.status(StatusCode::PARTIAL_CONTENT);
}
+
+ Ok(resp.body(SizedStream::new(length, reader)))
}
}
diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml
deleted file mode 100644
index 7e322e1d4..000000000
--- a/actix-framed/Cargo.toml
+++ /dev/null
@@ -1,37 +0,0 @@
-[package]
-name = "actix-framed"
-version = "0.3.0"
-authors = ["Nikolay Kim "]
-description = "Actix framed app server"
-readme = "README.md"
-keywords = ["http", "web", "framework", "async", "futures"]
-homepage = "https://actix.rs"
-repository = "https://github.com/actix/actix-web.git"
-documentation = "https://docs.rs/actix-framed/"
-categories = ["network-programming", "asynchronous",
- "web-programming::http-server",
- "web-programming::websocket"]
-license = "MIT/Apache-2.0"
-edition = "2018"
-
-[lib]
-name = "actix_framed"
-path = "src/lib.rs"
-
-[dependencies]
-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"
-
-bytes = "0.5.3"
-futures = "0.3.1"
-pin-project = "0.4.6"
-log = "0.4"
-
-[dev-dependencies]
-actix-server = "1.0.0"
-actix-connect = { version = "1.0.0", features=["openssl"] }
-actix-http-test = { version = "1.0.0", features=["openssl"] }
-actix-utils = "1.0.3"
diff --git a/actix-framed/LICENSE-APACHE b/actix-framed/LICENSE-APACHE
deleted file mode 100644
index 6cdf2d16c..000000000
--- a/actix-framed/LICENSE-APACHE
+++ /dev/null
@@ -1,201 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "{}"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright 2017-NOW Nikolay Kim
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/actix-framed/LICENSE-MIT b/actix-framed/LICENSE-MIT
deleted file mode 100644
index 0f80296ae..000000000
--- a/actix-framed/LICENSE-MIT
+++ /dev/null
@@ -1,25 +0,0 @@
-Copyright (c) 2017 Nikolay Kim
-
-Permission is hereby granted, free of charge, to any
-person obtaining a copy of this software and associated
-documentation files (the "Software"), to deal in the
-Software without restriction, including without
-limitation the rights to use, copy, modify, merge,
-publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software
-is furnished to do so, subject to the following
-conditions:
-
-The above copyright notice and this permission notice
-shall be included in all copies or substantial portions
-of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
-ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
-TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
-PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
-SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
-IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.
diff --git a/actix-framed/README.md b/actix-framed/README.md
index 1714b3640..a4eaadf21 100644
--- a/actix-framed/README.md
+++ b/actix-framed/README.md
@@ -1,8 +1,3 @@
-# Framed app for actix web [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-framed) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+# Framed app for actix web
-## Documentation & community resources
-
-* [API Documentation](https://docs.rs/actix-framed/)
-* [Chat on gitter](https://gitter.im/actix/actix)
-* Cargo package: [actix-framed](https://crates.io/crates/actix-framed)
-* Minimum supported Rust version: 1.33 or later
+**This crate has been deprecated and removed.**
diff --git a/actix-framed/changes.md b/actix-framed/changes.md
deleted file mode 100644
index 41c7aed0e..000000000
--- a/actix-framed/changes.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Changes
-
-## [0.3.0] - 2019-12-25
-
-* Migrate to actix-http 1.0
-
-## [0.2.1] - 2019-07-20
-
-* Remove unneeded actix-utils dependency
-
-
-## [0.2.0] - 2019-05-12
-
-* Update dependencies
-
-
-## [0.1.0] - 2019-04-16
-
-* Update tests
-
-
-## [0.1.0-alpha.1] - 2019-04-12
-
-* Initial release
diff --git a/actix-framed/src/app.rs b/actix-framed/src/app.rs
deleted file mode 100644
index e4b91e6c4..000000000
--- a/actix-framed/src/app.rs
+++ /dev/null
@@ -1,221 +0,0 @@
-use std::future::Future;
-use std::pin::Pin;
-use std::rc::Rc;
-use std::task::{Context, Poll};
-
-use actix_codec::{AsyncRead, AsyncWrite, Framed};
-use actix_http::h1::{Codec, SendResponse};
-use actix_http::{Error, Request, Response};
-use actix_router::{Path, Router, Url};
-use actix_service::{IntoServiceFactory, Service, ServiceFactory};
-use futures::future::{ok, FutureExt, LocalBoxFuture};
-
-use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService};
-use crate::request::FramedRequest;
-use crate::state::State;
-
-type BoxedResponse = LocalBoxFuture<'static, Result<(), Error>>;
-
-pub trait HttpServiceFactory {
- type Factory: ServiceFactory;
-
- fn path(&self) -> &str;
-
- fn create(self) -> Self::Factory;
-}
-
-/// Application builder
-pub struct FramedApp {
- state: State,
- services: Vec<(String, BoxedHttpNewService>)>,
-}
-
-impl FramedApp {
- pub fn new() -> Self {
- FramedApp {
- state: State::new(()),
- services: Vec::new(),
- }
- }
-}
-
-impl FramedApp {
- pub fn with(state: S) -> FramedApp {
- FramedApp {
- services: Vec::new(),
- state: State::new(state),
- }
- }
-
- pub fn service(mut self, factory: U) -> Self
- where
- U: HttpServiceFactory,
- U::Factory: ServiceFactory<
- Config = (),
- Request = FramedRequest,
- Response = (),
- Error = Error,
- InitError = (),
- > + 'static,
- ::Future: 'static,
- ::Service: Service<
- Request = FramedRequest,
- Response = (),
- Error = Error,
- Future = LocalBoxFuture<'static, Result<(), Error>>,
- >,
- {
- let path = factory.path().to_string();
- self.services
- .push((path, Box::new(HttpNewService::new(factory.create()))));
- self
- }
-}
-
-impl IntoServiceFactory> for FramedApp
-where
- T: AsyncRead + AsyncWrite + Unpin + 'static,
- S: 'static,
-{
- fn into_factory(self) -> FramedAppFactory {
- FramedAppFactory {
- state: self.state,
- services: Rc::new(self.services),
- }
- }
-}
-
-#[derive(Clone)]
-pub struct FramedAppFactory {
- state: State,
- services: Rc>)>>,
-}
-
-impl ServiceFactory for FramedAppFactory
-where
- T: AsyncRead + AsyncWrite + Unpin + 'static,
- S: 'static,
-{
- type Config = ();
- type Request = (Request, Framed);
- type Response = ();
- type Error = Error;
- type InitError = ();
- type Service = FramedAppService;
- type Future = CreateService;
-
- fn new_service(&self, _: ()) -> Self::Future {
- CreateService {
- fut: self
- .services
- .iter()
- .map(|(path, service)| {
- CreateServiceItem::Future(
- Some(path.clone()),
- service.new_service(()),
- )
- })
- .collect(),
- state: self.state.clone(),
- }
- }
-}
-
-#[doc(hidden)]
-pub struct CreateService {
- fut: Vec>,
- state: State,
-}
-
-enum CreateServiceItem {
- Future(
- Option,
- LocalBoxFuture<'static, Result>, ()>>,
- ),
- Service(String, BoxedHttpService>),
-}
-
-impl Future for CreateService
-where
- T: AsyncRead + AsyncWrite + Unpin,
-{
- type Output = Result, ()>;
-
- fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll {
- let mut done = true;
-
- // poll http services
- for item in &mut self.fut {
- let res = match item {
- CreateServiceItem::Future(ref mut path, ref mut fut) => {
- match Pin::new(fut).poll(cx) {
- Poll::Ready(Ok(service)) => {
- Some((path.take().unwrap(), service))
- }
- Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
- Poll::Pending => {
- done = false;
- None
- }
- }
- }
- CreateServiceItem::Service(_, _) => continue,
- };
-
- if let Some((path, service)) = res {
- *item = CreateServiceItem::Service(path, service);
- }
- }
-
- if done {
- let router = self
- .fut
- .drain(..)
- .fold(Router::build(), |mut router, item| {
- match item {
- CreateServiceItem::Service(path, service) => {
- router.path(&path, service);
- }
- CreateServiceItem::Future(_, _) => unreachable!(),
- }
- router
- });
- Poll::Ready(Ok(FramedAppService {
- router: router.finish(),
- state: self.state.clone(),
- }))
- } else {
- Poll::Pending
- }
- }
-}
-
-pub struct FramedAppService {
- state: State,
- router: Router>>,
-}
-
-impl Service for FramedAppService
-where
- T: AsyncRead + AsyncWrite + Unpin,
-{
- type Request = (Request, Framed);
- type Response = ();
- type Error = Error;
- type Future = BoxedResponse;
-
- fn poll_ready(&mut self, _: &mut Context) -> Poll> {
- Poll::Ready(Ok(()))
- }
-
- fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future {
- let mut path = Path::new(Url::new(req.uri().clone()));
-
- if let Some((srv, _info)) = self.router.recognize_mut(&mut path) {
- return srv.call(FramedRequest::new(req, framed, path, self.state.clone()));
- }
- SendResponse::new(framed, Response::NotFound().finish())
- .then(|_| ok(()))
- .boxed_local()
- }
-}
diff --git a/actix-framed/src/helpers.rs b/actix-framed/src/helpers.rs
deleted file mode 100644
index 29492e45b..000000000
--- a/actix-framed/src/helpers.rs
+++ /dev/null
@@ -1,98 +0,0 @@
-use std::task::{Context, Poll};
-
-use actix_http::Error;
-use actix_service::{Service, ServiceFactory};
-use futures::future::{FutureExt, LocalBoxFuture};
-
-pub(crate) type BoxedHttpService = Box<
- dyn Service<
- Request = Req,
- Response = (),
- Error = Error,
- Future = LocalBoxFuture<'static, Result<(), Error>>,
- >,
->;
-
-pub(crate) type BoxedHttpNewService = Box<
- dyn ServiceFactory<
- Config = (),
- Request = Req,
- Response = (),
- Error = Error,
- InitError = (),
- Service = BoxedHttpService,
- Future = LocalBoxFuture<'static, Result, ()>>,
- >,
->;
-
-pub(crate) struct HttpNewService(T);
-
-impl HttpNewService
-where
- T: ServiceFactory,
- T::Response: 'static,
- T::Future: 'static,
- T::Service: Service>> + 'static,
- ::Future: 'static,
-{
- pub fn new(service: T) -> Self {
- HttpNewService(service)
- }
-}
-
-impl ServiceFactory for HttpNewService
-where
- T: ServiceFactory,
- T::Request: 'static,
- T::Future: 'static,
- T::Service: Service>> + 'static,
- ::Future: 'static,
-{
- type Config = ();
- type Request = T::Request;
- type Response = ();
- type Error = Error;
- type InitError = ();
- type Service = BoxedHttpService;
- type Future = LocalBoxFuture<'static, Result>;
-
- fn new_service(&self, _: ()) -> Self::Future {
- let fut = self.0.new_service(());
-
- async move {
- fut.await.map_err(|_| ()).map(|service| {
- let service: BoxedHttpService<_> =
- Box::new(HttpServiceWrapper { service });
- service
- })
- }
- .boxed_local()
- }
-}
-
-struct HttpServiceWrapper {
- service: T,
-}
-
-impl Service for HttpServiceWrapper
-where
- T: Service<
- Response = (),
- Future = LocalBoxFuture<'static, Result<(), Error>>,
- Error = Error,
- >,
- T::Request: 'static,
-{
- type Request = T::Request;
- type Response = ();
- type Error = Error;
- type Future = LocalBoxFuture<'static, Result<(), Error>>;
-
- fn poll_ready(&mut self, cx: &mut Context) -> Poll> {
- self.service.poll_ready(cx)
- }
-
- fn call(&mut self, req: Self::Request) -> Self::Future {
- self.service.call(req)
- }
-}
diff --git a/actix-framed/src/lib.rs b/actix-framed/src/lib.rs
deleted file mode 100644
index 250533f39..000000000
--- a/actix-framed/src/lib.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-#![allow(clippy::type_complexity, clippy::new_without_default, dead_code)]
-mod app;
-mod helpers;
-mod request;
-mod route;
-mod service;
-mod state;
-pub mod test;
-
-// re-export for convinience
-pub use actix_http::{http, Error, HttpMessage, Response, ResponseError};
-
-pub use self::app::{FramedApp, FramedAppService};
-pub use self::request::FramedRequest;
-pub use self::route::FramedRoute;
-pub use self::service::{SendError, VerifyWebSockets};
-pub use self::state::State;
diff --git a/actix-framed/src/request.rs b/actix-framed/src/request.rs
deleted file mode 100644
index 1872dcc25..000000000
--- a/actix-framed/src/request.rs
+++ /dev/null
@@ -1,172 +0,0 @@
-use std::cell::{Ref, RefMut};
-
-use actix_codec::Framed;
-use actix_http::http::{HeaderMap, Method, Uri, Version};
-use actix_http::{h1::Codec, Extensions, Request, RequestHead};
-use actix_router::{Path, Url};
-
-use crate::state::State;
-
-pub struct FramedRequest {
- req: Request,
- framed: Framed,
- state: State,
- pub(crate) path: Path,
-}
-
-impl FramedRequest {
- pub fn new(
- req: Request,
- framed: Framed,
- path: Path,
- state: State,
- ) -> Self {
- Self {
- req,
- framed,
- state,
- path,
- }
- }
-}
-
-impl FramedRequest {
- /// Split request into a parts
- pub fn into_parts(self) -> (Request, Framed, State) {
- (self.req, self.framed, self.state)
- }
-
- /// This method returns reference to the request head
- #[inline]
- pub fn head(&self) -> &RequestHead {
- self.req.head()
- }
-
- /// This method returns muttable reference to the request head.
- /// panics if multiple references of http request exists.
- #[inline]
- pub fn head_mut(&mut self) -> &mut RequestHead {
- self.req.head_mut()
- }
-
- /// Shared application state
- #[inline]
- pub fn state(&self) -> &S {
- self.state.get_ref()
- }
-
- /// Request's uri.
- #[inline]
- pub fn uri(&self) -> &Uri {
- &self.head().uri
- }
-
- /// Read the Request method.
- #[inline]
- pub fn method(&self) -> &Method {
- &self.head().method
- }
-
- /// Read the Request Version.
- #[inline]
- pub fn version(&self) -> Version {
- self.head().version
- }
-
- #[inline]
- /// Returns request's headers.
- pub fn headers(&self) -> &HeaderMap {
- &self.head().headers
- }
-
- /// The target path of this Request.
- #[inline]
- pub fn path(&self) -> &str {
- self.head().uri.path()
- }
-
- /// The query string in the URL.
- ///
- /// E.g., id=10
- #[inline]
- pub fn query_string(&self) -> &str {
- if let Some(query) = self.uri().query().as_ref() {
- query
- } else {
- ""
- }
- }
-
- /// Get a reference to the Path parameters.
- ///
- /// Params is a container for url parameters.
- /// A variable segment is specified in the form `{identifier}`,
- /// where the identifier can be used later in a request handler to
- /// access the matched value for that segment.
- #[inline]
- pub fn match_info(&self) -> &Path {
- &self.path
- }
-
- /// Request extensions
- #[inline]
- pub fn extensions(&self) -> Ref {
- self.head().extensions()
- }
-
- /// Mutable reference to a the request's extensions
- #[inline]
- pub fn extensions_mut(&self) -> RefMut {
- self.head().extensions_mut()
- }
-}
-
-#[cfg(test)]
-mod tests {
- use std::convert::TryFrom;
-
- use actix_http::http::{HeaderName, HeaderValue};
- use actix_http::test::{TestBuffer, TestRequest};
-
- use super::*;
-
- #[test]
- fn test_reqest() {
- let buf = TestBuffer::empty();
- let framed = Framed::new(buf, Codec::default());
- let req = TestRequest::with_uri("/index.html?q=1")
- .header("content-type", "test")
- .finish();
- let path = Path::new(Url::new(req.uri().clone()));
-
- let mut freq = FramedRequest::new(req, framed, path, State::new(10u8));
- assert_eq!(*freq.state(), 10);
- assert_eq!(freq.version(), Version::HTTP_11);
- assert_eq!(freq.method(), Method::GET);
- assert_eq!(freq.path(), "/index.html");
- assert_eq!(freq.query_string(), "q=1");
- assert_eq!(
- freq.headers()
- .get("content-type")
- .unwrap()
- .to_str()
- .unwrap(),
- "test"
- );
-
- freq.head_mut().headers.insert(
- HeaderName::try_from("x-hdr").unwrap(),
- HeaderValue::from_static("test"),
- );
- assert_eq!(
- freq.headers().get("x-hdr").unwrap().to_str().unwrap(),
- "test"
- );
-
- freq.extensions_mut().insert(100usize);
- assert_eq!(*freq.extensions().get::().unwrap(), 100usize);
-
- let (_, _, state) = freq.into_parts();
- assert_eq!(*state, 10);
- }
-}
diff --git a/actix-framed/src/route.rs b/actix-framed/src/route.rs
deleted file mode 100644
index 793f46273..000000000
--- a/actix-framed/src/route.rs
+++ /dev/null
@@ -1,159 +0,0 @@
-use std::fmt;
-use std::future::Future;
-use std::marker::PhantomData;
-use std::task::{Context, Poll};
-
-use actix_codec::{AsyncRead, AsyncWrite};
-use actix_http::{http::Method, Error};
-use actix_service::{Service, ServiceFactory};
-use futures::future::{ok, FutureExt, LocalBoxFuture, Ready};
-use log::error;
-
-use crate::app::HttpServiceFactory;
-use crate::request::FramedRequest;
-
-/// Resource route definition
-///
-/// Route uses builder-like pattern for configuration.
-/// If handler is not explicitly set, default *404 Not Found* handler is used.
-pub struct FramedRoute {
- handler: F,
- pattern: String,
- methods: Vec,
- state: PhantomData<(Io, S, R, E)>,
-}
-
-impl FramedRoute {
- pub fn new(pattern: &str) -> Self {
- FramedRoute {
- handler: (),
- pattern: pattern.to_string(),
- methods: Vec::new(),
- state: PhantomData,
- }
- }
-
- pub fn get(path: &str) -> FramedRoute {
- FramedRoute::new(path).method(Method::GET)
- }
-
- pub fn post(path: &str) -> FramedRoute {
- FramedRoute::new(path).method(Method::POST)
- }
-
- pub fn put(path: &str) -> FramedRoute {
- FramedRoute::new(path).method(Method::PUT)
- }
-
- pub fn delete(path: &str) -> FramedRoute {
- FramedRoute::new(path).method(Method::DELETE)
- }
-
- pub fn method(mut self, method: Method) -> Self {
- self.methods.push(method);
- self
- }
-
- pub fn to(self, handler: F) -> FramedRoute
- where
- F: FnMut(FramedRequest) -> R,
- R: Future