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/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 128f51ffd..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -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 deleted file mode 100644 index 08bb81d48..000000000 --- a/.github/workflows/bench.yml +++ /dev/null @@ -1,47 +0,0 @@ -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/macos.yml b/.github/workflows/macos.yml deleted file mode 100644 index 397236a29..000000000 --- a/.github/workflows/macos.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: CI (macOS) - -on: [push, pull_request] - -jobs: - build_and_test: - strategy: - fail-fast: false - matrix: - version: - - stable - - nightly - - name: ${{ matrix.version }} - x86_64-apple-darwin - runs-on: macOS-latest - - steps: - - uses: actions/checkout@master - - - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.version }}-x86_64-apple-darwin - 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: - command: check - args: --all --bins --examples --tests - - - name: tests - uses: actions-rs/cargo@v1 - with: - command: test - 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/windows.yml b/.github/workflows/windows.yml deleted file mode 100644 index 36b224ba6..000000000 --- a/.github/workflows/windows.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: CI (Windows) - -on: [push, pull_request] - -env: - VCPKGRS_DYNAMIC: 1 - -jobs: - build_and_test: - strategy: - fail-fast: false - matrix: - version: - - stable - - nightly - - name: ${{ matrix.version }} - x86_64-pc-windows-msvc - runs-on: windows-latest - - steps: - - uses: actions/checkout@master - - - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.version }}-x86_64-pc-windows-msvc - profile: minimal - override: true - - - name: Install OpenSSL - 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 - with: - command: check - args: --all --bins --examples --tests - - - name: tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --all --all-features --no-fail-fast -- --nocapture - --skip=test_h2_content_length - --skip=test_reading_deflate_encoding_large_random_rustls - --skip=test_params - --skip=test_simple - --skip=test_expect_continue - --skip=test_http10_keepalive - --skip=test_slow_request - --skip=test_connection_force_close - --skip=test_connection_server_close diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 42d0755dd..000000000 --- a/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -Cargo.lock -target/ -guide/build/ -/gh-pages - -*.so -*.out -*.pyc -*.pid -*.sock -*~ - -# These are backup files generated by rustfmt -**/*.rs.bk 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 deleted file mode 100644 index b42635b86..000000000 --- a/CHANGES.md +++ /dev/null @@ -1,422 +0,0 @@ -# Changes - - -## [2.0.NEXT] - 2020-01-xx - -### 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.5 - -## [2.0.0] - 2019-12-25 - -### Changed - -* Rename `HttpServer::start()` to `HttpServer::run()` - -* Allow to gracefully stop test server via `TestServer::stop()` - -* Allow to specify multi-patterns for resources - -## [2.0.0-rc] - 2019-12-20 - -### Changed - -* Move `BodyEncoding` to `dev` module #1220 - -* Allow to set `peer_addr` for TestRequest #1074 - -* Make web::Data deref to Arc #1214 - -* Rename `App::register_data()` to `App::app_data()` - -* `HttpRequest::app_data()` returns `Option<&T>` instead of `Option<&Data>` - -### Fixed - -* Fix `AppConfig::secure()` is always false. #1202 - - -## [2.0.0-alpha.6] - 2019-12-15 - -### Fixed - -* Fixed compilation with default features off - -## [2.0.0-alpha.5] - 2019-12-13 - -### Added - -* Add test server, `test::start()` and `test::start_with()` - -## [2.0.0-alpha.4] - 2019-12-08 - -### Deleted - -* Delete HttpServer::run(), it is not useful witht async/await - -## [2.0.0-alpha.3] - 2019-12-07 - -### Changed - -* Migrate to tokio 0.2 - - -## [2.0.0-alpha.1] - 2019-11-22 - -### Changed - -* Migrated to `std::future` - -* Remove implementation of `Responder` for `()`. (#1167) - - -## [1.0.9] - 2019-11-14 - -### Added - -* Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110) - -### Changed - -* Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129) - - -## [1.0.8] - 2019-09-25 - -### Added - -* Add `Scope::register_data` and `Resource::register_data` methods, parallel to - `App::register_data`. - -* Add `middleware::Condition` that conditionally enables another middleware - -* Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload` - -* Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path, - which is useful for example with systemd. - -### Changed - -* Make UrlEncodedError::Overflow more informativve - -* Use actix-testing for testing utils - - -## [1.0.7] - 2019-08-29 - -### Fixed - -* Request Extensions leak #1062 - - -## [1.0.6] - 2019-08-28 - -### Added - -* Re-implement Host predicate (#989) - -* Form immplements Responder, returning a `application/x-www-form-urlencoded` response - -* Add `into_inner` to `Data` - -* Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set - the header in test requests. - -### Changed - -* `Query` payload made `pub`. Allows user to pattern-match the payload. - -* Enable `rust-tls` feature for client #1045 - -* Update serde_urlencoded to 0.6.1 - -* Update url to 2.1 - - -## [1.0.5] - 2019-07-18 - -### Added - -* Unix domain sockets (HttpServer::bind_uds) #92 - -* Actix now logs errors resulting in "internal server error" responses always, with the `error` - logging level - -### Fixed - -* Restored logging of errors through the `Logger` middleware - - -## [1.0.4] - 2019-07-17 - -### Added - -* Add `Responder` impl for `(T, StatusCode) where T: Responder` - -* Allow to access app's resource map via - `ServiceRequest::resource_map()` and `HttpRequest::resource_map()` methods. - -### Changed - -* Upgrade `rand` dependency version to 0.7 - - -## [1.0.3] - 2019-06-28 - -### Added - -* Support asynchronous data factories #850 - -### Changed - -* Use `encoding_rs` crate instead of unmaintained `encoding` crate - - -## [1.0.2] - 2019-06-17 - -### Changed - -* Move cors middleware to `actix-cors` crate. - -* Move identity middleware to `actix-identity` crate. - - -## [1.0.1] - 2019-06-17 - -### Added - -* Add support for PathConfig #903 - -* Add `middleware::identity::RequestIdentity` trait to `get_identity` from `HttpMessage`. - -### Changed - -* Move cors middleware to `actix-cors` crate. - -* Move identity middleware to `actix-identity` crate. - -* Disable default feature `secure-cookies`. - -* Allow to test an app that uses async actors #897 - -* Re-apply patch from #637 #894 - -### Fixed - -* HttpRequest::url_for is broken with nested scopes #915 - - -## [1.0.0] - 2019-06-05 - -### Added - -* Add `Scope::configure()` method. - -* Add `ServiceRequest::set_payload()` method. - -* Add `test::TestRequest::set_json()` convenience method to automatically - serialize data and set header in test requests. - -* Add macros for head, options, trace, connect and patch http methods - -### Changed - -* Drop an unnecessary `Option<_>` indirection around `ServerBuilder` from `HttpServer`. #863 - -### Fixed - -* Fix Logger request time format, and use rfc3339. #867 - -* Clear http requests pool on app service drop #860 - - -## [1.0.0-rc] - 2019-05-18 - -### Add - -* Add `Query::from_query()` to extract parameters from a query string. #846 -* `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors. - -### Changed - -* `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too. - -### Fixed - -* Codegen with parameters in the path only resolves the first registered endpoint #841 - - -## [1.0.0-beta.4] - 2019-05-12 - -### Add - -* Allow to set/override app data on scope level - -### Changed - -* `App::configure` take an `FnOnce` instead of `Fn` -* Upgrade actix-net crates - - -## [1.0.0-beta.3] - 2019-05-04 - -### Added - -* Add helper function for executing futures `test::block_fn()` - -### Changed - -* Extractor configuration could be registered with `App::data()` - or with `Resource::data()` #775 - -* Route data is unified with app data, `Route::data()` moved to resource - level to `Resource::data()` - -* CORS handling without headers #702 - -* Allow to construct `Data` instances to avoid double `Arc` for `Send + Sync` types. - -### Fixed - -* Fix `NormalizePath` middleware impl #806 - -### Deleted - -* `App::data_factory()` is deleted. - - -## [1.0.0-beta.2] - 2019-04-24 - -### Added - -* Add raw services support via `web::service()` - -* Add helper functions for reading response body `test::read_body()` - -* Add support for `remainder match` (i.e "/path/{tail}*") - -* Extend `Responder` trait, allow to override status code and headers. - -* Store visit and login timestamp in the identity cookie #502 - -### Changed - -* `.to_async()` handler can return `Responder` type #792 - -### Fixed - -* Fix async web::Data factory handling - - -## [1.0.0-beta.1] - 2019-04-20 - -### Added - -* Add helper functions for reading test response body, - `test::read_response()` and test::read_response_json()` - -* Add `.peer_addr()` #744 - -* Add `NormalizePath` middleware - -### Changed - -* Rename `RouterConfig` to `ServiceConfig` - -* Rename `test::call_success` to `test::call_service` - -* Removed `ServiceRequest::from_parts()` as it is unsafe to create from parts. - -* `CookieIdentityPolicy::max_age()` accepts value in seconds - -### Fixed - -* Fixed `TestRequest::app_data()` - - -## [1.0.0-alpha.6] - 2019-04-14 - -### Changed - -* Allow to use any service as default service. - -* Remove generic type for request payload, always use default. - -* Removed `Decompress` middleware. Bytes, String, Json, Form extractors - automatically decompress payload. - -* Make extractor config type explicit. Add `FromRequest::Config` associated type. - - -## [1.0.0-alpha.5] - 2019-04-12 - -### Added - -* Added async io `TestBuffer` for testing. - -### Deleted - -* Removed native-tls support - - -## [1.0.0-alpha.4] - 2019-04-08 - -### Added - -* `App::configure()` allow to offload app configuration to different methods - -* Added `URLPath` option for logger - -* Added `ServiceRequest::app_data()`, returns `Data` - -* Added `ServiceFromRequest::app_data()`, returns `Data` - -### Changed - -* `FromRequest` trait refactoring - -* Move multipart support to actix-multipart crate - -### Fixed - -* Fix body propagation in Response::from_error. #760 - - -## [1.0.0-alpha.3] - 2019-04-02 - -### Changed - -* Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()` - -* Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()` - -* Removed `Deref` impls - -### Removed - -* Removed unused `actix_web::web::md()` - - -## [1.0.0-alpha.2] - 2019-03-29 - -### Added - -* rustls support - -### Changed - -* use forked cookie - -* multipart::Field renamed to MultipartField - -## [1.0.0-alpha.1] - 2019-03-28 - -### Changed - -* Complete architecture re-design. - -* Return 405 response if no matching route found within resource #538 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 599b28c0d..000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index a6783a6db..000000000 --- a/Cargo.toml +++ /dev/null @@ -1,127 +0,0 @@ -[package] -name = "actix-web" -version = "2.0.0" -authors = ["Nikolay Kim "] -description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." -readme = "README.md" -keywords = ["actix", "http", "web", "framework", "async"] -homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-web/" -categories = ["network-programming", "asynchronous", - "web-programming::http-server", - "web-programming::websocket"] -license = "MIT/Apache-2.0" -edition = "2018" - -[package.metadata.docs.rs] -features = ["openssl", "rustls", "compress", "secure-cookies"] - -[badges] -travis-ci = { repository = "actix/actix-web", branch = "master" } -codecov = { repository = "actix/actix-web", branch = "master", service = "github" } - -[lib] -name = "actix_web" -path = "src/lib.rs" - -[workspace] -members = [ - ".", - "awc", - "actix-http", - "actix-cors", - "actix-files", - "actix-framed", - "actix-session", - "actix-identity", - "actix-multipart", - "actix-web-actors", - "actix-web-codegen", - "test-server", -] - -[features] -default = ["compress", "failure"] - -# content-encoding support -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"] - -[dependencies] -actix-codec = "0.2.0" -actix-service = "1.0.2" -actix-utils = "1.0.6" -actix-router = "0.2.4" -actix-rt = "1.0.0" -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-web-codegen = "0.2.0" -actix-http = "1.0.1" -awc = { version = "1.0.1", default-features = false } - -bytes = "0.5.3" -derive_more = "0.99.2" -encoding_rs = "0.8" -futures = "0.3.1" -fxhash = "0.2.1" -log = "0.4" -mime = "0.3" -net2 = "0.2.33" -pin-project = "0.4.6" -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"] } -url = "2.1" -open-ssl = { version="0.10", package = "openssl", optional = true } -rust-tls = { version = "0.16.0", package = "rustls", optional = true } - -[dev-dependencies] -actix = "0.9.0" -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 -opt-level = 3 -codegen-units = 1 - -[patch.crates-io] -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" } - -[[bench]] -name = "server" -harness = false - -[[bench]] -name = "service" -harness = false diff --git a/LICENSE-APACHE b/LICENSE-APACHE deleted file mode 100644 index 6cdf2d16c..000000000 --- a/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/LICENSE-MIT b/LICENSE-MIT deleted file mode 100644 index 0f80296ae..000000000 --- a/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/MIGRATION.md b/MIGRATION.md deleted file mode 100644 index aef382a21..000000000 --- a/MIGRATION.md +++ /dev/null @@ -1,601 +0,0 @@ -## 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. - -## 2.0.0 - -* `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to - `.await` on `run` method result, in that case it awaits server exit. - -* `App::register_data()` renamed to `App::app_data()` and accepts any type `T: 'static`. - Stored data is available via `HttpRequest::app_data()` method at runtime. - -* Extractor configuration must be registered with `App::app_data()` instead of `App::data()` - -* Sync handlers has been removed. `.to_async()` method has been renamed to `.to()` - replace `fn` with `async fn` to convert sync handler to async - -* `actix_http_test::TestServer` moved to `actix_web::test` module. To start - test server use `test::start()` or `test_start_with_config()` methods - -* `ResponseError` trait has been reafctored. `ResponseError::error_response()` renders - http response. - -* Feature `rust-tls` renamed to `rustls` - - instead of - - ```rust - actix-web = { version = "2.0.0", features = ["rust-tls"] } - ``` - - use - - ```rust - actix-web = { version = "2.0.0", features = ["rustls"] } - ``` - -* Feature `ssl` renamed to `openssl` - - instead of - - ```rust - actix-web = { version = "2.0.0", features = ["ssl"] } - ``` - - use - - ```rust - actix-web = { version = "2.0.0", features = ["openssl"] } - ``` -* `Cors` builder now requires that you call `.finish()` to construct the middleware - -## 1.0.1 - -* Cors middleware has been moved to `actix-cors` crate - - instead of - - ```rust - use actix_web::middleware::cors::Cors; - ``` - - use - - ```rust - use actix_cors::Cors; - ``` - -* Identity middleware has been moved to `actix-identity` crate - - instead of - - ```rust - use actix_web::middleware::identity::{Identity, CookieIdentityPolicy, IdentityService}; - ``` - - use - - ```rust - use actix_identity::{Identity, CookieIdentityPolicy, IdentityService}; - ``` - - -## 1.0.0 - -* Extractor configuration. In version 1.0 this is handled with the new `Data` mechanism for both setting and retrieving the configuration - - instead of - - ```rust - - #[derive(Default)] - struct ExtractorConfig { - config: String, - } - - impl FromRequest for YourExtractor { - type Config = ExtractorConfig; - type Result = Result; - - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - println!("use the config: {:?}", cfg.config); - ... - } - } - - App::new().resource("/route_with_config", |r| { - r.post().with_config(handler_fn, |cfg| { - cfg.0.config = "test".to_string(); - }) - }) - - ``` - - use the HttpRequest to get the configuration like any other `Data` with `req.app_data::()` and set it with the `data()` method on the `resource` - - ```rust - #[derive(Default)] - struct ExtractorConfig { - config: String, - } - - impl FromRequest for YourExtractor { - type Error = Error; - type Future = Result; - type Config = ExtractorConfig; - - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - let cfg = req.app_data::(); - println!("config data?: {:?}", cfg.unwrap().role); - ... - } - } - - App::new().service( - resource("/route_with_config") - .data(ExtractorConfig { - config: "test".to_string(), - }) - .route(post().to(handler_fn)), - ) - ``` - -* Resource registration. 1.0 version uses generalized resource - registration via `.service()` method. - - instead of - - ```rust - App.new().resource("/welcome", |r| r.f(welcome)) - ``` - - use App's or Scope's `.service()` method. `.service()` method accepts - object that implements `HttpServiceFactory` trait. By default - actix-web provides `Resource` and `Scope` services. - - ```rust - App.new().service( - web::resource("/welcome") - .route(web::get().to(welcome)) - .route(web::post().to(post_handler)) - ``` - -* Scope registration. - - instead of - - ```rust - let app = App::new().scope("/{project_id}", |scope| { - scope - .resource("/path1", |r| r.f(|_| HttpResponse::Ok())) - .resource("/path2", |r| r.f(|_| HttpResponse::Ok())) - .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed())) - }); - ``` - - use `.service()` for registration and `web::scope()` as scope object factory. - - ```rust - let app = App::new().service( - web::scope("/{project_id}") - .service(web::resource("/path1").to(|| HttpResponse::Ok())) - .service(web::resource("/path2").to(|| HttpResponse::Ok())) - .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed())) - ); - ``` - -* `.with()`, `.with_async()` registration methods have been renamed to `.to()` and `.to_async()`. - - instead of - - ```rust - App.new().resource("/welcome", |r| r.with(welcome)) - ``` - - use `.to()` or `.to_async()` methods - - ```rust - App.new().service(web::resource("/welcome").to(welcome)) - ``` - -* Passing arguments to handler with extractors, multiple arguments are allowed - - instead of - - ```rust - fn welcome((body, req): (Bytes, HttpRequest)) -> ... { - ... - } - ``` - - use multiple arguments - - ```rust - fn welcome(body: Bytes, req: HttpRequest) -> ... { - ... - } - ``` - -* `.f()`, `.a()` and `.h()` handler registration methods have been removed. - Use `.to()` for handlers and `.to_async()` for async handlers. Handler function - must use extractors. - - instead of - - ```rust - App.new().resource("/welcome", |r| r.f(welcome)) - ``` - - use App's `to()` or `to_async()` methods - - ```rust - App.new().service(web::resource("/welcome").to(welcome)) - ``` - -* `HttpRequest` does not provide access to request's payload stream. - - instead of - - ```rust - fn index(req: &HttpRequest) -> Box> { - req - .payload() - .from_err() - .fold((), |_, chunk| { - ... - }) - .map(|_| HttpResponse::Ok().finish()) - .responder() - } - ``` - - use `Payload` extractor - - ```rust - fn index(stream: web::Payload) -> impl Future { - stream - .from_err() - .fold((), |_, chunk| { - ... - }) - .map(|_| HttpResponse::Ok().finish()) - } - ``` - -* `State` is now `Data`. You register Data during the App initialization process - and then access it from handlers either using a Data extractor or using - HttpRequest's api. - - instead of - - ```rust - App.with_state(T) - ``` - - use App's `data` method - - ```rust - App.new() - .data(T) - ``` - - and either use the Data extractor within your handler - - ```rust - use actix_web::web::Data; - - fn endpoint_handler(Data)){ - ... - } - ``` - - .. or access your Data element from the HttpRequest - - ```rust - fn endpoint_handler(req: HttpRequest) { - let data: Option> = req.app_data::(); - } - ``` - - -* AsyncResponder is removed, use `.to_async()` registration method and `impl Future<>` as result type. - - instead of - - ```rust - use actix_web::AsyncResponder; - - fn endpoint_handler(...) -> impl Future{ - ... - .responder() - } - ``` - - .. simply omit AsyncResponder and the corresponding responder() finish method - - -* Middleware - - instead of - - ```rust - let app = App::new() - .middleware(middleware::Logger::default()) - ``` - - use `.wrap()` method - - ```rust - let app = App::new() - .wrap(middleware::Logger::default()) - .route("/index.html", web::get().to(index)); - ``` - -* `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()` - method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead. - - instead of - - ```rust - fn index(req: &HttpRequest) -> Responder { - req.body() - .and_then(|body| { - ... - }) - } - ``` - - use - - ```rust - fn index(body: Bytes) -> Responder { - ... - } - ``` - -* `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. - - instead of `use actix_web::fs::StaticFile` - - use `use actix_files::Files` - - instead of `use actix_web::fs::Namedfile` - - use `use actix_files::NamedFile` - -* Multipart has been move to separate create. - - instead of `use actix_web::multipart::Multipart` - - use `use actix_multipart::Multipart` - -* Response compression is not enabled by default. - To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`. - -* Session middleware moved to actix-session crate - -* Actors support have been moved to `actix-web-actors` crate - -* Custom Error - - Instead of error_response method alone, ResponseError now provides two methods: error_response and render_response respectively. Where, error_response creates the error response and render_response returns the error response to the caller. - - Simplest migration from 0.7 to 1.0 shall include below method to the custom implementation of ResponseError: - - ```rust - fn render_response(&self) -> HttpResponse { - self.error_response() - } - ``` - -## 0.7.15 - -* The `' '` character is not percent decoded anymore before matching routes. If you need to use it in - your routes, you should use `%20`. - - instead of - - ```rust - fn main() { - let app = App::new().resource("/my index", |r| { - r.method(http::Method::GET) - .with(index); - }); - } - ``` - - use - - ```rust - fn main() { - let app = App::new().resource("/my%20index", |r| { - r.method(http::Method::GET) - .with(index); - }); - } - ``` - -* If you used `AsyncResult::async` you need to replace it with `AsyncResult::future` - - -## 0.7.4 - -* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple - even for handler with one parameter. - - -## 0.7 - -* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload - use `HttpMessage::payload()` method. - - instead of - - ```rust - fn index(req: HttpRequest) -> impl Responder { - req - .from_err() - .fold(...) - .... - } - ``` - - use `.payload()` - - ```rust - fn index(req: HttpRequest) -> impl Responder { - req - .payload() // <- get request payload stream - .from_err() - .fold(...) - .... - } - ``` - -* [Middleware](https://actix.rs/actix-web/actix_web/middleware/trait.Middleware.html) - trait uses `&HttpRequest` instead of `&mut HttpRequest`. - -* Removed `Route::with2()` and `Route::with3()` use tuple of extractors instead. - - instead of - - ```rust - fn index(query: Query<..>, info: Json impl Responder {} - ``` - - use tuple of extractors and use `.with()` for registration: - - ```rust - fn index((query, json): (Query<..>, Json impl Responder {} - ``` - -* `Handler::handle()` uses `&self` instead of `&mut self` - -* `Handler::handle()` accepts reference to `HttpRequest<_>` instead of value - -* Removed deprecated `HttpServer::threads()`, use - [HttpServer::workers()](https://actix.rs/actix-web/actix_web/server/struct.HttpServer.html#method.workers) instead. - -* Renamed `client::ClientConnectorError::Connector` to - `client::ClientConnectorError::Resolver` - -* `Route::with()` does not return `ExtractorConfig`, to configure - extractor use `Route::with_config()` - - instead of - - ```rust - fn main() { - let app = App::new().resource("/index.html", |r| { - r.method(http::Method::GET) - .with(index) - .limit(4096); // <- limit size of the payload - }); - } - ``` - - use - - ```rust - - fn main() { - let app = App::new().resource("/index.html", |r| { - r.method(http::Method::GET) - .with_config(index, |cfg| { // <- register handler - cfg.limit(4096); // <- limit size of the payload - }) - }); - } - ``` - -* `Route::with_async()` does not return `ExtractorConfig`, to configure - extractor use `Route::with_async_config()` - - -## 0.6 - -* `Path` extractor return `ErrorNotFound` on failure instead of `ErrorBadRequest` - -* `ws::Message::Close` now includes optional close reason. - `ws::CloseCode::Status` and `ws::CloseCode::Empty` have been removed. - -* `HttpServer::threads()` renamed to `HttpServer::workers()`. - -* `HttpServer::start_ssl()` and `HttpServer::start_tls()` deprecated. - Use `HttpServer::bind_ssl()` and `HttpServer::bind_tls()` instead. - -* `HttpRequest::extensions()` returns read only reference to the request's Extension - `HttpRequest::extensions_mut()` returns mutable reference. - -* Instead of - - `use actix_web::middleware::{ - CookieSessionBackend, CookieSessionError, RequestSession, - Session, SessionBackend, SessionImpl, SessionStorage};` - - use `actix_web::middleware::session` - - `use actix_web::middleware::session{CookieSessionBackend, CookieSessionError, - RequestSession, Session, SessionBackend, SessionImpl, SessionStorage};` - -* `FromRequest::from_request()` accepts mutable reference to a request - -* `FromRequest::Result` has to implement `Into>` - -* [`Responder::respond_to()`]( - https://actix.rs/actix-web/actix_web/trait.Responder.html#tymethod.respond_to) - is generic over `S` - -* Use `Query` extractor instead of HttpRequest::query()`. - - ```rust - fn index(q: Query>) -> Result<..> { - ... - } - ``` - - or - - ```rust - let q = Query::>::extract(req); - ``` - -* Websocket operations are implemented as `WsWriter` trait. - you need to use `use actix_web::ws::WsWriter` - - -## 0.5 - -* `HttpResponseBuilder::body()`, `.finish()`, `.json()` - methods return `HttpResponse` instead of `Result` - -* `actix_web::Method`, `actix_web::StatusCode`, `actix_web::Version` - moved to `actix_web::http` module - -* `actix_web::header` moved to `actix_web::http::header` - -* `NormalizePath` moved to `actix_web::http` module - -* `HttpServer` moved to `actix_web::server`, added new `actix_web::server::new()` function, - shortcut for `actix_web::server::HttpServer::new()` - -* `DefaultHeaders` middleware does not use separate builder, all builder methods moved to type itself - -* `StaticFiles::new()`'s show_index parameter removed, use `show_files_listing()` method instead. - -* `CookieSessionBackendBuilder` removed, all methods moved to `CookieSessionBackend` type - -* `actix_web::httpcodes` module is deprecated, `HttpResponse::Ok()`, `HttpResponse::Found()` and other `HttpResponse::XXX()` - functions should be used instead - -* `ClientRequestBuilder::body()` returns `Result<_, actix_web::Error>` - instead of `Result<_, http::Error>` - -* `Application` renamed to a `App` - -* `actix_web::Reply`, `actix_web::Resource` moved to `actix_web::dev` diff --git a/README.md b/README.md deleted file mode 100644 index 0b55c5bae..000000000 --- a/README.md +++ /dev/null @@ -1,106 +0,0 @@ -
-

Actix web

-

Actix web is a small, pragmatic, and extremely fast rust web framework

-

- -[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) -[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) -[![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) -[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Documentation](https://docs.rs/actix-web/badge.svg)](https://docs.rs/actix-web) -[![Download](https://img.shields.io/crates/d/actix-web.svg)](https://crates.io/crates/actix-web) -[![Version](https://img.shields.io/badge/rustc-1.39+-lightgray.svg)](https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html) -![License](https://img.shields.io/crates/l/actix-web.svg) - -

- -

- Website - | - Chat - | - Examples -

-
-
- -Actix web is a simple, pragmatic and extremely fast web framework for Rust. - -* Supported *HTTP/1.x* and *HTTP/2.0* protocols -* Streaming and pipelining -* Keep-alive and slow requests handling -* Client/server [WebSockets](https://actix.rs/docs/websockets/) support -* Transparent content compression/decompression (br, gzip, deflate) -* Configurable [request routing](https://actix.rs/docs/url-dispatch/) -* Multipart streams -* Static assets -* SSL support with OpenSSL or Rustls -* 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) - -## Example - -Dependencies: - -```toml -[dependencies] -actix-web = "2" -actix-rt = "1" -``` - -Code: - -```rust -use actix_web::{get, web, App, HttpServer, Responder}; - -#[get("/{id}/{name}/index.html")] -async fn index(info: web::Path<(u32, String)>) -> impl Responder { - format!("Hello {}! id:{}", info.1, info.0) -} - -#[actix_rt::main] -async fn main() -> std::io::Result<()> { - HttpServer::new(|| App::new().service(index)) - .bind("127.0.0.1:8080")? - .run() - .await -} -``` - -### More examples - -* [Basics](https://github.com/actix/examples/tree/master/basics/) -* [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/) / -* [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/) -* [OpenSSL](https://github.com/actix/examples/tree/master/openssl/) -* [Rustls](https://github.com/actix/examples/tree/master/rustls/) -* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/) -* [Json](https://github.com/actix/examples/tree/master/json/) - -You may consider checking out -[this directory](https://github.com/actix/examples/tree/master/) for more examples. - -## Benchmarks - -* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r18) - -## License - -This project is licensed under either of - -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) - -at your option. - -## Code of Conduct - -Contribution to the actix-web crate is organized under the terms of the -Contributor Covenant, the maintainer of actix-web, @fafhrd91, promises to -intervene to uphold that code of conduct. diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md deleted file mode 100644 index c4918b56d..000000000 --- a/actix-files/CHANGES.md +++ /dev/null @@ -1,76 +0,0 @@ -# Changes - -## [0.2.1] - 2019-12-22 - -* Use the same format for file URLs regardless of platforms - -## [0.2.0] - 2019-12-20 - -* Fix BodyEncoding trait import #1220 - -## [0.2.0-alpha.1] - 2019-12-07 - -* Migrate to `std::future` - -## [0.1.7] - 2019-11-06 - -* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) - -## [0.1.6] - 2019-10-14 - -* Add option to redirect to a slash-ended path `Files` #1132 - -## [0.1.5] - 2019-10-08 - -* Bump up `mime_guess` crate version to 2.0.1 - -* Bump up `percent-encoding` crate version to 2.1 - -* Allow user defined request guards for `Files` #1113 - -## [0.1.4] - 2019-07-20 - -* Allow to disable `Content-Disposition` header #686 - -## [0.1.3] - 2019-06-28 - -* Do not set `Content-Length` header, let actix-http set it #930 - -## [0.1.2] - 2019-06-13 - -* Content-Length is 0 for NamedFile HEAD request #914 - -* Fix ring dependency from actix-web default features for #741 - -## [0.1.1] - 2019-06-01 - -* Static files are incorrectly served as both chunked and with length #812 - -## [0.1.0] - 2019-05-25 - -* NamedFile last-modified check always fails due to nano-seconds - in file modified date #820 - -## [0.1.0-beta.4] - 2019-05-12 - -* Update actix-web to beta.4 - -## [0.1.0-beta.1] - 2019-04-20 - -* Update actix-web to beta.1 - -## [0.1.0-alpha.6] - 2019-04-14 - -* Update actix-web to alpha6 - -## [0.1.0-alpha.4] - 2019-04-08 - -* Update actix-web to alpha4 - -## [0.1.0-alpha.2] - 2019-04-02 - -* Add default handler support - -## [0.1.0-alpha.1] - 2019-03-28 - -* Initial impl diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml deleted file mode 100644 index 104eb3dfa..000000000 --- a/actix-files/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "actix-files" -version = "0.2.1" -authors = ["Nikolay Kim "] -description = "Static files support for actix web." -readme = "README.md" -keywords = ["actix", "http", "async", "futures"] -homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-files/" -categories = ["asynchronous", "web-programming::http-server"] -license = "MIT/Apache-2.0" -edition = "2018" -workspace = ".." - -[lib] -name = "actix_files" -path = "src/lib.rs" - -[dependencies] -actix-web = { version = "2.0.0-rc", default-features = false } -actix-http = "1.0.1" -actix-service = "1.0.1" -bitflags = "1" -bytes = "0.5.3" -futures = "0.3.1" -derive_more = "0.99.2" -log = "0.4" -mime = "0.3" -mime_guess = "2.0.1" -percent-encoding = "2.1" -v_htmlescape = "0.4" - -[dev-dependencies] -actix-rt = "1.0.0" -actix-web = { version = "2.0.0-rc", features=["openssl"] } diff --git a/actix-files/LICENSE-APACHE b/actix-files/LICENSE-APACHE deleted file mode 120000 index 965b606f3..000000000 --- a/actix-files/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/actix-files/LICENSE-MIT b/actix-files/LICENSE-MIT deleted file mode 120000 index 76219eb72..000000000 --- a/actix-files/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/actix-files/README.md b/actix-files/README.md deleted file mode 100644 index 9585e67a8..000000000 --- a/actix-files/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Static files support for actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-files)](https://crates.io/crates/actix-files) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -## Documentation & community resources - -* [User Guide](https://actix.rs/docs/) -* [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 diff --git a/actix-files/src/error.rs b/actix-files/src/error.rs deleted file mode 100644 index 49a46e58d..000000000 --- a/actix-files/src/error.rs +++ /dev/null @@ -1,41 +0,0 @@ -use actix_web::{http::StatusCode, HttpResponse, ResponseError}; -use derive_more::Display; - -/// Errors which can occur when serving static files. -#[derive(Display, Debug, PartialEq)] -pub enum FilesError { - /// Path is not a directory - #[display(fmt = "Path is not a directory. Unable to serve static files")] - IsNotDirectory, - - /// Cannot render directory - #[display(fmt = "Unable to render directory without index file")] - IsDirectory, -} - -/// Return `NotFound` for `FilesError` -impl ResponseError for FilesError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::NOT_FOUND) - } -} - -#[derive(Display, Debug, PartialEq)] -pub enum UriSegmentError { - /// The segment started with the wrapped invalid character. - #[display(fmt = "The segment started with the wrapped invalid character")] - BadStart(char), - /// The segment contained the wrapped invalid character. - #[display(fmt = "The segment contained the wrapped invalid character")] - BadChar(char), - /// The segment ended with the wrapped invalid character. - #[display(fmt = "The segment ended with the wrapped invalid character")] - BadEnd(char), -} - -/// Return `BadRequest` for `UriSegmentError` -impl ResponseError for UriSegmentError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs deleted file mode 100644 index d910b7d5f..000000000 --- a/actix-files/src/lib.rs +++ /dev/null @@ -1,1429 +0,0 @@ -#![allow(clippy::borrow_interior_mutable_const, clippy::type_complexity)] - -//! Static files support -use std::cell::RefCell; -use std::fmt::Write; -use std::fs::{DirEntry, File}; -use std::future::Future; -use std::io::{Read, Seek}; -use std::path::{Path, PathBuf}; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{cmp, io}; - -use actix_service::boxed::{self, BoxService, BoxServiceFactory}; -use actix_service::{IntoServiceFactory, Service, ServiceFactory}; -use actix_web::dev::{ - AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest, - ServiceResponse, -}; -use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; -use actix_web::guard::Guard; -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 mime; -use mime_guess::from_ext; -use percent_encoding::{utf8_percent_encode, CONTROLS}; -use v_htmlescape::escape as escape_html_entity; - -mod error; -mod named; -mod range; - -use self::error::{FilesError, UriSegmentError}; -pub use crate::named::NamedFile; -pub use crate::range::HttpRange; - -type HttpService = BoxService; -type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; - -/// Return the MIME type associated with a filename extension (case-insensitive). -/// If `ext` is empty or no associated type for the extension was found, returns -/// the type `application/octet-stream`. -#[inline] -pub fn file_extension_to_mime(ext: &str) -> mime::Mime { - from_ext(ext).first_or_octet_stream() -} - -fn handle_error(err: BlockingError) -> Error { - match err { - BlockingError::Error(err) => err.into(), - BlockingError::Canceled => ErrorInternalServerError("Unexpected error"), - } -} -#[doc(hidden)] -/// A helper created from a `std::fs::File` which reads the file -/// chunk-by-chunk on a `ThreadPool`. -pub struct ChunkedReadFile { - size: u64, - offset: u64, - file: Option, - fut: - Option>>>, - counter: u64, -} - -impl Stream for ChunkedReadFile { - type Item = Result; - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context, - ) -> Poll> { - if let Some(ref mut fut) = self.fut { - return match Pin::new(fut).poll(cx) { - Poll::Ready(Ok((file, bytes))) => { - self.fut.take(); - self.file = Some(file); - self.offset += bytes.len() as u64; - self.counter += bytes.len() as u64; - Poll::Ready(Some(Ok(bytes))) - } - Poll::Ready(Err(e)) => Poll::Ready(Some(Err(handle_error(e)))), - Poll::Pending => Poll::Pending, - }; - } - - let size = self.size; - let offset = self.offset; - let counter = self.counter; - - if size == counter { - Poll::Ready(None) - } else { - let mut file = self.file.take().expect("Use after completion"); - self.fut = Some( - web::block(move || { - let max_bytes: usize; - max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; - let mut buf = Vec::with_capacity(max_bytes); - file.seek(io::SeekFrom::Start(offset))?; - let nbytes = - file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; - if nbytes == 0 { - return Err(io::ErrorKind::UnexpectedEof.into()); - } - Ok((file, Bytes::from(buf))) - }) - .boxed_local(), - ); - self.poll_next(cx) - } - } -} - -type DirectoryRenderer = - dyn Fn(&Directory, &HttpRequest) -> Result; - -/// A directory; responds with the generated directory listing. -#[derive(Debug)] -pub struct Directory { - /// Base directory - pub base: PathBuf, - /// Path of subdirectory to generate listing for - pub path: PathBuf, -} - -impl Directory { - /// Create a new directory - pub fn new(base: PathBuf, path: PathBuf) -> Directory { - Directory { base, path } - } - - /// Is this entry visible from this directory? - pub fn is_visible(&self, entry: &io::Result) -> bool { - if let Ok(ref entry) = *entry { - if let Some(name) = entry.file_name().to_str() { - if name.starts_with('.') { - return false; - } - } - if let Ok(ref md) = entry.metadata() { - let ft = md.file_type(); - return ft.is_dir() || ft.is_file() || ft.is_symlink(); - } - } - false - } -} - -// show file url as relative to static path -macro_rules! encode_file_url { - ($path:ident) => { - utf8_percent_encode(&$path, CONTROLS) - }; -} - -// " -- " & -- & ' -- ' < -- < > -- > / -- / -macro_rules! encode_file_name { - ($entry:ident) => { - escape_html_entity(&$entry.file_name().to_string_lossy()) - }; -} - -fn directory_listing( - dir: &Directory, - req: &HttpRequest, -) -> Result { - let index_of = format!("Index of {}", req.path()); - let mut body = String::new(); - let base = Path::new(req.path()); - - for entry in dir.path.read_dir()? { - if dir.is_visible(&entry) { - let entry = entry.unwrap(); - let p = match entry.path().strip_prefix(&dir.path) { - Ok(p) if cfg!(windows) => { - base.join(p).to_string_lossy().replace("\\", "/") - } - Ok(p) => base.join(p).to_string_lossy().into_owned(), - Err(_) => continue, - }; - - // if file is a directory, add '/' to the end of the name - if let Ok(metadata) = entry.metadata() { - if metadata.is_dir() { - let _ = write!( - body, - "
  • {}/
  • ", - encode_file_url!(p), - encode_file_name!(entry), - ); - } else { - let _ = write!( - body, - "
  • {}
  • ", - encode_file_url!(p), - encode_file_name!(entry), - ); - } - } else { - continue; - } - } - } - - let html = format!( - "\ - {}\ -

    {}

    \ -
      \ - {}\ -
    \n", - index_of, index_of, body - ); - Ok(ServiceResponse::new( - req.clone(), - HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body(html), - )) -} - -type MimeOverride = dyn Fn(&mime::Name) -> DispositionType; - -/// Static files handling -/// -/// `Files` service must be registered with `App::service()` method. -/// -/// ```rust -/// use actix_web::App; -/// use actix_files as fs; -/// -/// fn main() { -/// let app = App::new() -/// .service(fs::Files::new("/static", ".")); -/// } -/// ``` -pub struct Files { - path: String, - directory: PathBuf, - index: Option, - show_index: bool, - redirect_to_slash: bool, - default: Rc>>>, - renderer: Rc, - mime_override: Option>, - file_flags: named::Flags, - guards: Option>>, -} - -impl Clone for Files { - fn clone(&self) -> Self { - Self { - directory: self.directory.clone(), - index: self.index.clone(), - show_index: self.show_index, - redirect_to_slash: self.redirect_to_slash, - default: self.default.clone(), - renderer: self.renderer.clone(), - file_flags: self.file_flags, - path: self.path.clone(), - mime_override: self.mime_override.clone(), - guards: self.guards.clone(), - } - } -} - -impl Files { - /// Create new `Files` instance for specified base directory. - /// - /// `File` uses `ThreadPool` for blocking filesystem operations. - /// By default pool with 5x threads of available cpus is used. - /// Pool size can be changed by setting ACTIX_THREADPOOL environment variable. - pub fn new>(path: &str, dir: T) -> Files { - let orig_dir = dir.into(); - let dir = match orig_dir.canonicalize() { - Ok(canon_dir) => canon_dir, - Err(_) => { - log::error!("Specified path is not a directory: {:?}", orig_dir); - PathBuf::new() - } - }; - - Files { - path: path.to_string(), - directory: dir, - index: None, - show_index: false, - redirect_to_slash: false, - default: Rc::new(RefCell::new(None)), - renderer: Rc::new(directory_listing), - mime_override: None, - file_flags: named::Flags::default(), - guards: None, - } - } - - /// Show files listing for directories. - /// - /// By default show files listing is disabled. - pub fn show_files_listing(mut self) -> Self { - self.show_index = true; - self - } - - /// Redirects to a slash-ended path when browsing a directory. - /// - /// By default never redirect. - pub fn redirect_to_slash_directory(mut self) -> Self { - self.redirect_to_slash = true; - self - } - - /// Set custom directory renderer - pub fn files_listing_renderer(mut self, f: F) -> Self - where - for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) -> Result - + 'static, - { - self.renderer = Rc::new(f); - self - } - - /// Specifies mime override callback - pub fn mime_override(mut self, f: F) -> Self - where - F: Fn(&mime::Name) -> DispositionType + 'static, - { - self.mime_override = Some(Rc::new(f)); - self - } - - /// Set index file - /// - /// Shows specific index file for directory "/" instead of - /// showing files listing. - pub fn index_file>(mut self, index: T) -> Self { - self.index = Some(index.into()); - self - } - - #[inline] - /// Specifies whether to use ETag or not. - /// - /// Default is true. - pub fn use_etag(mut self, value: bool) -> Self { - self.file_flags.set(named::Flags::ETAG, value); - self - } - - #[inline] - /// Specifies whether to use Last-Modified or not. - /// - /// Default is true. - pub fn use_last_modified(mut self, value: bool) -> Self { - self.file_flags.set(named::Flags::LAST_MD, value); - self - } - - /// Specifies custom guards to use for directory listings and files. - /// - /// Default behaviour allows GET and HEAD. - #[inline] - pub fn use_guards(mut self, guards: G) -> Self { - self.guards = Some(Rc::new(Box::new(guards))); - self - } - - /// Disable `Content-Disposition` header. - /// - /// By default Content-Disposition` header is enabled. - #[inline] - pub fn disable_content_disposition(mut self) -> Self { - self.file_flags.remove(named::Flags::CONTENT_DISPOSITION); - self - } - - /// Sets default handler which is used when no matched file could be found. - pub fn default_handler(mut self, f: F) -> Self - where - F: IntoServiceFactory, - U: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - > + 'static, - { - // create and configure default resource - self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory( - f.into_factory().map_init_err(|_| ()), - ))))); - - self - } -} - -impl HttpServiceFactory for Files { - fn register(self, config: &mut AppService) { - if self.default.borrow().is_none() { - *self.default.borrow_mut() = Some(config.default_service()); - } - let rdef = if config.is_root() { - ResourceDef::root_prefix(&self.path) - } else { - ResourceDef::prefix(&self.path) - }; - config.register_service(rdef, None, self, None) - } -} - -impl ServiceFactory for Files { - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Config = (); - type Service = FilesService; - type InitError = (); - type Future = LocalBoxFuture<'static, Result>; - - fn new_service(&self, _: ()) -> Self::Future { - let mut srv = FilesService { - directory: self.directory.clone(), - index: self.index.clone(), - show_index: self.show_index, - redirect_to_slash: self.redirect_to_slash, - default: None, - renderer: self.renderer.clone(), - mime_override: self.mime_override.clone(), - file_flags: self.file_flags, - guards: self.guards.clone(), - }; - - if let Some(ref default) = *self.default.borrow() { - default - .new_service(()) - .map(move |result| match result { - Ok(default) => { - srv.default = Some(default); - Ok(srv) - } - Err(_) => Err(()), - }) - .boxed_local() - } else { - ok(srv).boxed_local() - } - } -} - -pub struct FilesService { - directory: PathBuf, - index: Option, - show_index: bool, - redirect_to_slash: bool, - default: Option, - renderer: Rc, - mime_override: Option>, - file_flags: named::Flags, - guards: Option>>, -} - -impl FilesService { - fn handle_err( - &mut self, - e: io::Error, - req: ServiceRequest, - ) -> Either< - Ready>, - LocalBoxFuture<'static, Result>, - > { - log::debug!("Files: Failed to handle {}: {}", req.path(), e); - if let Some(ref mut default) = self.default { - Either::Right(default.call(req)) - } else { - Either::Left(ok(req.error_response(e))) - } - } -} - -impl Service for FilesService { - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Future = Either< - Ready>, - LocalBoxFuture<'static, Result>, - >; - - fn poll_ready(&mut self, _: &mut Context) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: ServiceRequest) -> Self::Future { - let is_method_valid = if let Some(guard) = &self.guards { - // execute user defined guards - (**guard).check(req.head()) - } else { - // default behaviour - match *req.method() { - Method::HEAD | Method::GET => true, - _ => false, - } - }; - - if !is_method_valid { - return Either::Left(ok(req.into_response( - actix_web::HttpResponse::MethodNotAllowed() - .header(header::CONTENT_TYPE, "text/plain") - .body("Request did not meet this resource's requirements."), - ))); - } - - let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) { - Ok(item) => item, - Err(e) => return Either::Left(ok(req.error_response(e))), - }; - - // full filepath - let path = match self.directory.join(&real_path.0).canonicalize() { - Ok(path) => path, - Err(e) => return self.handle_err(e, req), - }; - - if path.is_dir() { - if let Some(ref redir_index) = self.index { - if self.redirect_to_slash && !req.path().ends_with('/') { - let redirect_to = format!("{}/", req.path()); - return Either::Left(ok(req.into_response( - HttpResponse::Found() - .header(header::LOCATION, redirect_to) - .body("") - .into_body(), - ))); - } - - let path = path.join(redir_index); - - match NamedFile::open(path) { - Ok(mut named_file) => { - if let Some(ref mime_override) = self.mime_override { - let new_disposition = - mime_override(&named_file.content_type.type_()); - named_file.content_disposition.disposition = new_disposition; - } - - named_file.flags = self.file_flags; - let (req, _) = req.into_parts(); - Either::Left(ok(match named_file.into_response(&req) { - Ok(item) => ServiceResponse::new(req, item), - Err(e) => ServiceResponse::from_err(e, req), - })) - } - Err(e) => self.handle_err(e, req), - } - } else if self.show_index { - let dir = Directory::new(self.directory.clone(), path); - let (req, _) = req.into_parts(); - let x = (self.renderer)(&dir, &req); - match x { - Ok(resp) => Either::Left(ok(resp)), - Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))), - } - } else { - Either::Left(ok(ServiceResponse::from_err( - FilesError::IsDirectory, - req.into_parts().0, - ))) - } - } else { - match NamedFile::open(path) { - Ok(mut named_file) => { - if let Some(ref mime_override) = self.mime_override { - let new_disposition = - mime_override(&named_file.content_type.type_()); - named_file.content_disposition.disposition = new_disposition; - } - - named_file.flags = self.file_flags; - let (req, _) = req.into_parts(); - match named_file.into_response(&req) { - Ok(item) => { - Either::Left(ok(ServiceResponse::new(req.clone(), item))) - } - Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))), - } - } - Err(e) => self.handle_err(e, req), - } - } - } -} - -#[derive(Debug)] -struct PathBufWrp(PathBuf); - -impl PathBufWrp { - fn get_pathbuf(path: &str) -> Result { - let mut buf = PathBuf::new(); - for segment in path.split('/') { - if segment == ".." { - buf.pop(); - } else if segment.starts_with('.') { - return Err(UriSegmentError::BadStart('.')); - } else if segment.starts_with('*') { - return Err(UriSegmentError::BadStart('*')); - } else if segment.ends_with(':') { - return Err(UriSegmentError::BadEnd(':')); - } else if segment.ends_with('>') { - return Err(UriSegmentError::BadEnd('>')); - } else if segment.ends_with('<') { - return Err(UriSegmentError::BadEnd('<')); - } else if segment.is_empty() { - continue; - } else if cfg!(windows) && segment.contains('\\') { - return Err(UriSegmentError::BadChar('\\')); - } else { - buf.push(segment) - } - } - - Ok(PathBufWrp(buf)) - } -} - -impl FromRequest for PathBufWrp { - type Error = UriSegmentError; - type Future = Ready>; - type Config = (); - - fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - ready(PathBufWrp::get_pathbuf(req.match_info().path())) - } -} - -#[cfg(test)] -mod tests { - use std::fs; - use std::iter::FromIterator; - use std::ops::Add; - use std::time::{Duration, SystemTime}; - - use super::*; - use actix_web::guard; - use actix_web::http::header::{ - self, ContentDisposition, DispositionParam, DispositionType, - }; - use actix_web::http::{Method, StatusCode}; - use actix_web::middleware::Compress; - use actix_web::test::{self, TestRequest}; - use actix_web::{App, Responder}; - - #[actix_rt::test] - async fn test_file_extension_to_mime() { - let m = file_extension_to_mime("jpg"); - assert_eq!(m, mime::IMAGE_JPEG); - - let m = file_extension_to_mime("invalid extension!!"); - assert_eq!(m, mime::APPLICATION_OCTET_STREAM); - - let m = file_extension_to_mime(""); - assert_eq!(m, mime::APPLICATION_OCTET_STREAM); - } - - #[actix_rt::test] - async fn test_if_modified_since_without_if_none_match() { - let file = NamedFile::open("Cargo.toml").unwrap(); - let since = - header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); - - let req = TestRequest::default() - .header(header::IF_MODIFIED_SINCE, since) - .to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); - } - - #[actix_rt::test] - async fn test_if_modified_since_with_if_none_match() { - let file = NamedFile::open("Cargo.toml").unwrap(); - let since = - header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); - - let req = TestRequest::default() - .header(header::IF_NONE_MATCH, "miss_etag") - .header(header::IF_MODIFIED_SINCE, since) - .to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); - } - - #[actix_rt::test] - async fn test_named_file_text() { - assert!(NamedFile::open("test--").is_err()); - let mut file = NamedFile::open("Cargo.toml").unwrap(); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - } - - #[actix_rt::test] - async fn test_named_file_content_disposition() { - assert!(NamedFile::open("test--").is_err()); - let mut file = NamedFile::open("Cargo.toml").unwrap(); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - - let file = NamedFile::open("Cargo.toml") - .unwrap() - .disable_content_disposition(); - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none()); - } - - #[actix_rt::test] - async fn test_named_file_non_ascii_file_name() { - let mut file = - NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml") - .unwrap(); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml" - ); - } - - #[actix_rt::test] - async fn test_named_file_set_content_type() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_content_type(mime::TEXT_XML); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/xml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - } - - #[actix_rt::test] - async fn test_named_file_image() { - let mut file = NamedFile::open("tests/test.png").unwrap(); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"test.png\"" - ); - } - - #[actix_rt::test] - async fn test_named_file_image_attachment() { - let cd = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename(String::from("test.png"))], - }; - let mut file = NamedFile::open("tests/test.png") - .unwrap() - .set_content_disposition(cd); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "image/png" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.png\"" - ); - } - - #[actix_rt::test] - async fn test_named_file_binary() { - let mut file = NamedFile::open("tests/test.binary").unwrap(); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "application/octet-stream" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"test.binary\"" - ); - } - - #[actix_rt::test] - async fn test_named_file_status_code_text() { - let mut file = NamedFile::open("Cargo.toml") - .unwrap() - .set_status_code(StatusCode::NOT_FOUND); - { - file.file(); - let _f: &File = &file; - } - { - let _f: &mut File = &mut file; - } - - let req = TestRequest::default().to_http_request(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/x-toml" - ); - assert_eq!( - resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - "inline; filename=\"Cargo.toml\"" - ); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - } - - #[actix_rt::test] - async fn test_mime_override() { - fn all_attachment(_: &mime::Name) -> DispositionType { - DispositionType::Attachment - } - - let mut srv = test::init_service( - App::new().service( - Files::new("/", ".") - .mime_override(all_attachment) - .index_file("Cargo.toml"), - ), - ) - .await; - - let request = TestRequest::get().uri("/").to_request(); - let response = test::call_service(&mut srv, request).await; - assert_eq!(response.status(), StatusCode::OK); - - let content_disposition = response - .headers() - .get(header::CONTENT_DISPOSITION) - .expect("To have CONTENT_DISPOSITION"); - let content_disposition = content_disposition - .to_str() - .expect("Convert CONTENT_DISPOSITION to str"); - assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\""); - } - - #[actix_rt::test] - async fn test_named_file_ranges_status_code() { - let mut srv = test::init_service( - App::new().service(Files::new("/test", ".").index_file("Cargo.toml")), - ) - .await; - - // Valid range header - let request = TestRequest::get() - .uri("/t%65st/Cargo.toml") - .header(header::RANGE, "bytes=10-20") - .to_request(); - let response = test::call_service(&mut srv, request).await; - assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); - - // Invalid range header - let request = TestRequest::get() - .uri("/t%65st/Cargo.toml") - .header(header::RANGE, "bytes=1-0") - .to_request(); - let response = test::call_service(&mut srv, request).await; - - assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); - } - - #[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; - - // Valid range header - let request = TestRequest::get() - .uri("/t%65st/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() - .unwrap(); - - assert_eq!(contentrange, "bytes 10-20/100"); - - // Invalid range header - let request = TestRequest::get() - .uri("/t%65st/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() - .unwrap(); - - assert_eq!(contentrange, "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; - - // Valid range header - let request = TestRequest::get() - .uri("/t%65st/tests/test.binary") - .header(header::RANGE, "bytes=10-20") - .to_request(); - let _response = test::call_service(&mut srv, request).await; - - // 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); - - // 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 contentlength = response - // .headers() - // .get(header::CONTENT_LENGTH) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(contentlength, "100"); - - // 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; - 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; - - // 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; - - // TODO: fix check - // let contentlength = response - // .headers() - // .get(header::CONTENT_LENGTH) - // .unwrap() - // .to_str() - // .unwrap(); - // assert_eq!(contentlength, "100"); - } - - #[actix_rt::test] - async fn test_static_files_with_spaces() { - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").index_file("Cargo.toml")), - ) - .await; - let request = TestRequest::get() - .uri("/tests/test%20space.binary") - .to_request(); - let response = test::call_service(&mut srv, request).await; - assert_eq!(response.status(), StatusCode::OK); - - let bytes = test::read_body(response).await; - let data = Bytes::from(fs::read("tests/test space.binary").unwrap()); - assert_eq!(bytes, data); - } - - #[actix_rt::test] - async fn test_files_not_allowed() { - let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; - - let req = TestRequest::default() - .uri("/Cargo.toml") - .method(Method::POST) - .to_request(); - - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; - let req = TestRequest::default() - .method(Method::PUT) - .uri("/Cargo.toml") - .to_request(); - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[actix_rt::test] - async fn test_files_guards() { - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").use_guards(guard::Post())), - ) - .await; - - let req = TestRequest::default() - .uri("/Cargo.toml") - .method(Method::POST) - .to_request(); - - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_named_file_content_encoding() { - let mut srv = test::init_service(App::new().wrap(Compress::default()).service( - web::resource("/").to(|| { - async { - NamedFile::open("Cargo.toml") - .unwrap() - .set_content_encoding(header::ContentEncoding::Identity) - } - }), - )) - .await; - - let request = TestRequest::get() - .uri("/") - .header(header::ACCEPT_ENCODING, "gzip") - .to_request(); - let res = test::call_service(&mut srv, request).await; - assert_eq!(res.status(), StatusCode::OK); - assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); - } - - #[actix_rt::test] - async fn test_named_file_content_encoding_gzip() { - let mut srv = test::init_service(App::new().wrap(Compress::default()).service( - web::resource("/").to(|| { - async { - NamedFile::open("Cargo.toml") - .unwrap() - .set_content_encoding(header::ContentEncoding::Gzip) - } - }), - )) - .await; - - let request = TestRequest::get() - .uri("/") - .header(header::ACCEPT_ENCODING, "gzip") - .to_request(); - let res = test::call_service(&mut srv, request).await; - assert_eq!(res.status(), StatusCode::OK); - assert_eq!( - res.headers() - .get(header::CONTENT_ENCODING) - .unwrap() - .to_str() - .unwrap(), - "gzip" - ); - } - - #[actix_rt::test] - async fn test_named_file_allowed_method() { - let req = TestRequest::default().method(Method::GET).to_http_request(); - let file = NamedFile::open("Cargo.toml").unwrap(); - let resp = file.respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_static_files() { - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").show_files_listing()), - ) - .await; - let req = TestRequest::with_uri("/missing").to_request(); - - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; - - let req = TestRequest::default().to_request(); - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").show_files_listing()), - ) - .await; - let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - "text/html; charset=utf-8" - ); - - let bytes = test::read_body(resp).await; - assert!(format!("{:?}", bytes).contains("/tests/test.png")); - } - - #[actix_rt::test] - async fn test_redirect_to_slash_directory() { - // should not redirect if no index - let mut srv = test::init_service( - App::new().service(Files::new("/", ".").redirect_to_slash_directory()), - ) - .await; - let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - // should redirect if index present - let mut srv = test::init_service( - App::new().service( - Files::new("/", ".") - .index_file("test.png") - .redirect_to_slash_directory(), - ), - ) - .await; - let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::FOUND); - - // should not redirect if the path is wrong - let req = TestRequest::with_uri("/not_existing").to_request(); - let resp = test::call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - } - - #[actix_rt::test] - async fn test_static_files_bad_directory() { - let _st: Files = Files::new("/", "missing"); - let _st: Files = Files::new("/", "Cargo.toml"); - } - - #[actix_rt::test] - async fn test_default_handler_file_missing() { - let mut st = Files::new("/", ".") - .default_handler(|req: ServiceRequest| { - ok(req.into_response(HttpResponse::Ok().body("default content"))) - }) - .new_service(()) - .await - .unwrap(); - let req = TestRequest::with_uri("/missing").to_srv_request(); - - let resp = test::call_service(&mut st, req).await; - assert_eq!(resp.status(), StatusCode::OK); - let bytes = test::read_body(resp).await; - assert_eq!(bytes, Bytes::from_static(b"default content")); - } - - // #[actix_rt::test] - // async fn test_serve_index() { - // let st = Files::new(".").index_file("test.binary"); - // let req = TestRequest::default().uri("/tests").finish(); - - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::OK); - // assert_eq!( - // resp.headers() - // .get(header::CONTENT_TYPE) - // .expect("content type"), - // "application/octet-stream" - // ); - // assert_eq!( - // resp.headers() - // .get(header::CONTENT_DISPOSITION) - // .expect("content disposition"), - // "attachment; filename=\"test.binary\"" - // ); - - // let req = TestRequest::default().uri("/tests/").finish(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::OK); - // assert_eq!( - // resp.headers().get(header::CONTENT_TYPE).unwrap(), - // "application/octet-stream" - // ); - // assert_eq!( - // resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - // "attachment; filename=\"test.binary\"" - // ); - - // // nonexistent index file - // let req = TestRequest::default().uri("/tests/unknown").finish(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - // let req = TestRequest::default().uri("/tests/unknown/").finish(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::NOT_FOUND); - // } - - // #[actix_rt::test] - // async fn test_serve_index_nested() { - // let st = Files::new(".").index_file("mod.rs"); - // let req = TestRequest::default().uri("/src/client").finish(); - // let resp = st.handle(&req).respond_to(&req).unwrap(); - // let resp = resp.as_msg(); - // assert_eq!(resp.status(), StatusCode::OK); - // assert_eq!( - // resp.headers().get(header::CONTENT_TYPE).unwrap(), - // "text/x-rust" - // ); - // assert_eq!( - // resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), - // "inline; filename=\"mod.rs\"" - // ); - // } - - // #[actix_rt::test] - // fn integration_serve_index() { - // let mut srv = test::TestServer::with_factory(|| { - // App::new().handler( - // "test", - // Files::new(".").index_file("Cargo.toml"), - // ) - // }); - - // let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::OK); - // let bytes = srv.execute(response.body()).unwrap(); - // let data = Bytes::from(fs::read("Cargo.toml").unwrap()); - // assert_eq!(bytes, data); - - // let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::OK); - // let bytes = srv.execute(response.body()).unwrap(); - // let data = Bytes::from(fs::read("Cargo.toml").unwrap()); - // assert_eq!(bytes, data); - - // // nonexistent index file - // let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::NOT_FOUND); - - // let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::NOT_FOUND); - // } - - // #[actix_rt::test] - // fn integration_percent_encoded() { - // let mut srv = test::TestServer::with_factory(|| { - // App::new().handler( - // "test", - // Files::new(".").index_file("Cargo.toml"), - // ) - // }); - - // let request = srv - // .get() - // .uri(srv.url("/test/%43argo.toml")) - // .finish() - // .unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::OK); - // } - - #[actix_rt::test] - async fn test_path_buf() { - assert_eq!( - PathBufWrp::get_pathbuf("/test/.tt").map(|t| t.0), - Err(UriSegmentError::BadStart('.')) - ); - assert_eq!( - PathBufWrp::get_pathbuf("/test/*tt").map(|t| t.0), - Err(UriSegmentError::BadStart('*')) - ); - assert_eq!( - PathBufWrp::get_pathbuf("/test/tt:").map(|t| t.0), - Err(UriSegmentError::BadEnd(':')) - ); - assert_eq!( - PathBufWrp::get_pathbuf("/test/tt<").map(|t| t.0), - Err(UriSegmentError::BadEnd('<')) - ); - assert_eq!( - PathBufWrp::get_pathbuf("/test/tt>").map(|t| t.0), - Err(UriSegmentError::BadEnd('>')) - ); - assert_eq!( - PathBufWrp::get_pathbuf("/seg1/seg2/").unwrap().0, - PathBuf::from_iter(vec!["seg1", "seg2"]) - ); - assert_eq!( - PathBufWrp::get_pathbuf("/seg1/../seg2/").unwrap().0, - PathBuf::from_iter(vec!["seg2"]) - ); - } -} diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs deleted file mode 100644 index fdb055998..000000000 --- a/actix-files/src/named.rs +++ /dev/null @@ -1,455 +0,0 @@ -use std::fs::{File, Metadata}; -use std::io; -use std::ops::{Deref, DerefMut}; -use std::path::{Path, PathBuf}; -use std::time::{SystemTime, UNIX_EPOCH}; - -#[cfg(unix)] -use std::os::unix::fs::MetadataExt; - -use bitflags::bitflags; -use mime; -use mime_guess::from_path; - -use actix_http::body::SizedStream; -use actix_web::dev::BodyEncoding; -use actix_web::http::header::{ - self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue, -}; -use actix_web::http::{ContentEncoding, StatusCode}; -use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; -use futures::future::{ready, Ready}; - -use crate::range::HttpRange; -use crate::ChunkedReadFile; - -bitflags! { - pub(crate) struct Flags: u8 { - const ETAG = 0b0000_0001; - const LAST_MD = 0b0000_0010; - const CONTENT_DISPOSITION = 0b0000_0100; - } -} - -impl Default for Flags { - fn default() -> Self { - Flags::all() - } -} - -/// A file with an associated name. -#[derive(Debug)] -pub struct NamedFile { - path: PathBuf, - file: File, - modified: Option, - pub(crate) md: Metadata, - pub(crate) flags: Flags, - pub(crate) status_code: StatusCode, - pub(crate) content_type: mime::Mime, - pub(crate) content_disposition: header::ContentDisposition, - pub(crate) encoding: Option, -} - -impl NamedFile { - /// Creates an instance from a previously opened file. - /// - /// The given `path` need not exist and is only used to determine the `ContentType` and - /// `ContentDisposition` headers. - /// - /// # Examples - /// - /// ```rust - /// use actix_files::NamedFile; - /// use std::io::{self, Write}; - /// use std::env; - /// use std::fs::File; - /// - /// fn main() -> io::Result<()> { - /// let mut file = File::create("foo.txt")?; - /// file.write_all(b"Hello, world!")?; - /// let named_file = NamedFile::from_file(file, "bar.txt")?; - /// # std::fs::remove_file("foo.txt"); - /// Ok(()) - /// } - /// ``` - pub fn from_file>(file: File, path: P) -> io::Result { - let path = path.as_ref().to_path_buf(); - - // Get the name of the file and use it to construct default Content-Type - // and Content-Disposition values - let (content_type, content_disposition) = { - let filename = match path.file_name() { - Some(name) => name.to_string_lossy(), - None => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Provided path has no filename", - )); - } - }; - - let ct = from_path(&path).first_or_octet_stream(); - let disposition_type = match ct.type_() { - mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, - _ => DispositionType::Attachment, - }; - let mut parameters = - vec![DispositionParam::Filename(String::from(filename.as_ref()))]; - if !filename.is_ascii() { - parameters.push(DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Ext(String::from("UTF-8")), - language_tag: None, - value: filename.into_owned().into_bytes(), - })) - } - let cd = ContentDisposition { - disposition: disposition_type, - parameters: parameters, - }; - (ct, cd) - }; - - let md = file.metadata()?; - let modified = md.modified().ok(); - let encoding = None; - Ok(NamedFile { - path, - file, - content_type, - content_disposition, - md, - modified, - encoding, - status_code: StatusCode::OK, - flags: Flags::default(), - }) - } - - /// Attempts to open a file in read-only mode. - /// - /// # Examples - /// - /// ```rust - /// use actix_files::NamedFile; - /// - /// let file = NamedFile::open("foo.txt"); - /// ``` - pub fn open>(path: P) -> io::Result { - Self::from_file(File::open(&path)?, path) - } - - /// Returns reference to the underlying `File` object. - #[inline] - pub fn file(&self) -> &File { - &self.file - } - - /// Retrieve the path of this file. - /// - /// # Examples - /// - /// ```rust - /// # use std::io; - /// use actix_files::NamedFile; - /// - /// # fn path() -> io::Result<()> { - /// let file = NamedFile::open("test.txt")?; - /// assert_eq!(file.path().as_os_str(), "foo.txt"); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub fn path(&self) -> &Path { - self.path.as_path() - } - - /// Set response **Status Code** - pub fn set_status_code(mut self, status: StatusCode) -> Self { - self.status_code = status; - self - } - - /// Set the MIME Content-Type for serving this file. By default - /// the Content-Type is inferred from the filename extension. - #[inline] - pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { - self.content_type = mime_type; - self - } - - /// Set the Content-Disposition for serving this file. This allows - /// changing the inline/attachment disposition as well as the filename - /// sent to the peer. By default the disposition is `inline` for text, - /// image, and video content types, and `attachment` otherwise, and - /// the filename is taken from the path provided in the `open` method - /// after converting it to UTF-8 using. - /// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy). - #[inline] - pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { - self.content_disposition = cd; - self.flags.insert(Flags::CONTENT_DISPOSITION); - self - } - - /// Disable `Content-Disposition` header. - /// - /// By default Content-Disposition` header is enabled. - #[inline] - pub fn disable_content_disposition(mut self) -> Self { - self.flags.remove(Flags::CONTENT_DISPOSITION); - self - } - - /// Set content encoding for serving this file - #[inline] - pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { - self.encoding = Some(enc); - self - } - - #[inline] - ///Specifies whether to use ETag or not. - /// - ///Default is true. - pub fn use_etag(mut self, value: bool) -> Self { - self.flags.set(Flags::ETAG, value); - self - } - - #[inline] - ///Specifies whether to use Last-Modified or not. - /// - ///Default is true. - pub fn use_last_modified(mut self, value: bool) -> Self { - self.flags.set(Flags::LAST_MD, value); - self - } - - pub(crate) fn etag(&self) -> Option { - // This etag format is similar to Apache's. - self.modified.as_ref().map(|mtime| { - let ino = { - #[cfg(unix)] - { - self.md.ino() - } - #[cfg(not(unix))] - { - 0 - } - }; - - let dur = mtime - .duration_since(UNIX_EPOCH) - .expect("modification time must be after epoch"); - header::EntityTag::strong(format!( - "{:x}:{:x}:{:x}:{:x}", - ino, - self.md.len(), - dur.as_secs(), - dur.subsec_nanos() - )) - }) - } - - pub(crate) fn last_modified(&self) -> Option { - self.modified.map(|mtime| mtime.into()) - } - - pub fn into_response(self, req: &HttpRequest) -> Result { - if self.status_code != StatusCode::OK { - let mut resp = HttpResponse::build(self.status_code); - resp.set(header::ContentType(self.content_type.clone())) - .if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| { - res.header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); - }); - if let Some(current_encoding) = self.encoding { - resp.encoding(current_encoding); - } - let reader = ChunkedReadFile { - size: self.md.len(), - offset: 0, - file: Some(self.file), - fut: None, - counter: 0, - }; - return Ok(resp.streaming(reader)); - } - - let etag = if self.flags.contains(Flags::ETAG) { - self.etag() - } else { - None - }; - let last_modified = if self.flags.contains(Flags::LAST_MD) { - self.last_modified() - } else { - None - }; - - // check preconditions - let precondition_failed = if !any_match(etag.as_ref(), req) { - true - } else if let (Some(ref m), Some(header::IfUnmodifiedSince(ref since))) = - (last_modified, req.get_header()) - { - let t1: SystemTime = m.clone().into(); - let t2: SystemTime = since.clone().into(); - match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) { - (Ok(t1), Ok(t2)) => t1 > t2, - _ => false, - } - } else { - false - }; - - // check last modified - let not_modified = if !none_match(etag.as_ref(), req) { - true - } else if req.headers().contains_key(&header::IF_NONE_MATCH) { - false - } else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) = - (last_modified, req.get_header()) - { - let t1: SystemTime = m.clone().into(); - let t2: SystemTime = since.clone().into(); - match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) { - (Ok(t1), Ok(t2)) => t1 <= t2, - _ => false, - } - } else { - false - }; - - let mut resp = HttpResponse::build(self.status_code); - resp.set(header::ContentType(self.content_type.clone())) - .if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| { - res.header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); - }); - // default compressing - if let Some(current_encoding) = self.encoding { - resp.encoding(current_encoding); - } - - resp.if_some(last_modified, |lm, resp| { - resp.set(header::LastModified(lm)); - }) - .if_some(etag, |etag, resp| { - resp.set(header::ETag(etag)); - }); - - resp.header(header::ACCEPT_RANGES, "bytes"); - - let mut length = self.md.len(); - let mut offset = 0; - - // check for range header - if let Some(ranges) = req.headers().get(&header::RANGE) { - if let Ok(rangesheader) = ranges.to_str() { - if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { - length = rangesvec[0].length; - offset = rangesvec[0].start; - resp.encoding(ContentEncoding::Identity); - resp.header( - header::CONTENT_RANGE, - format!( - "bytes {}-{}/{}", - offset, - offset + length - 1, - self.md.len() - ), - ); - } else { - resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); - return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); - }; - } else { - return Ok(resp.status(StatusCode::BAD_REQUEST).finish()); - }; - }; - - if precondition_failed { - return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); - } else if not_modified { - return Ok(resp.status(StatusCode::NOT_MODIFIED).finish()); - } - - let reader = ChunkedReadFile { - offset, - size: length, - file: Some(self.file), - 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))) - } - } -} - -impl Deref for NamedFile { - type Target = File; - - fn deref(&self) -> &File { - &self.file - } -} - -impl DerefMut for NamedFile { - fn deref_mut(&mut self) -> &mut File { - &mut self.file - } -} - -/// Returns true if `req` has no `If-Match` header or one which matches `etag`. -fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - None | Some(header::IfMatch::Any) => true, - Some(header::IfMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.strong_eq(some_etag) { - return true; - } - } - } - false - } - } -} - -/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`. -fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - Some(header::IfNoneMatch::Any) => false, - Some(header::IfNoneMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.weak_eq(some_etag) { - return false; - } - } - } - true - } - None => true, - } -} - -impl Responder for NamedFile { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - ready(self.into_response(req)) - } -} diff --git a/actix-files/src/range.rs b/actix-files/src/range.rs deleted file mode 100644 index 47673b0b0..000000000 --- a/actix-files/src/range.rs +++ /dev/null @@ -1,375 +0,0 @@ -/// HTTP Range header representation. -#[derive(Debug, Clone, Copy)] -pub struct HttpRange { - pub start: u64, - pub length: u64, -} - -static PREFIX: &str = "bytes="; -const PREFIX_LEN: usize = 6; - -impl HttpRange { - /// Parses Range HTTP header string as per RFC 2616. - /// - /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). - /// `size` is full size of response (file). - pub fn parse(header: &str, size: u64) -> Result, ()> { - if header.is_empty() { - return Ok(Vec::new()); - } - if !header.starts_with(PREFIX) { - return Err(()); - } - - let size_sig = size as i64; - let mut no_overlap = false; - - let all_ranges: Vec> = header[PREFIX_LEN..] - .split(',') - .map(|x| x.trim()) - .filter(|x| !x.is_empty()) - .map(|ra| { - let mut start_end_iter = ra.split('-'); - - let start_str = start_end_iter.next().ok_or(())?.trim(); - let end_str = start_end_iter.next().ok_or(())?.trim(); - - if start_str.is_empty() { - // If no start is specified, end specifies the - // range start relative to the end of the file. - let mut length: i64 = end_str.parse().map_err(|_| ())?; - - if length > size_sig { - length = size_sig; - } - - Ok(Some(HttpRange { - start: (size_sig - length) as u64, - length: length as u64, - })) - } else { - let start: i64 = start_str.parse().map_err(|_| ())?; - - if start < 0 { - return Err(()); - } - if start >= size_sig { - no_overlap = true; - return Ok(None); - } - - let length = if end_str.is_empty() { - // If no end is specified, range extends to end of the file. - size_sig - start - } else { - let mut end: i64 = end_str.parse().map_err(|_| ())?; - - if start > end { - return Err(()); - } - - if end >= size_sig { - end = size_sig - 1; - } - - end - start + 1 - }; - - Ok(Some(HttpRange { - start: start as u64, - length: length as u64, - })) - } - }) - .collect::>()?; - - let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); - - if no_overlap && ranges.is_empty() { - return Err(()); - } - - Ok(ranges) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - struct T(&'static str, u64, Vec); - - #[test] - fn test_parse() { - let tests = vec![ - T("", 0, vec![]), - T("", 1000, vec![]), - T("foo", 0, vec![]), - T("bytes=", 0, vec![]), - T("bytes=7", 10, vec![]), - T("bytes= 7 ", 10, vec![]), - T("bytes=1-", 0, vec![]), - T("bytes=5-4", 10, vec![]), - T("bytes=0-2,5-4", 10, vec![]), - T("bytes=2-5,4-3", 10, vec![]), - T("bytes=--5,4--3", 10, vec![]), - T("bytes=A-", 10, vec![]), - T("bytes=A- ", 10, vec![]), - T("bytes=A-Z", 10, vec![]), - T("bytes= -Z", 10, vec![]), - T("bytes=5-Z", 10, vec![]), - T("bytes=Ran-dom, garbage", 10, vec![]), - T("bytes=0x01-0x02", 10, vec![]), - T("bytes= ", 10, vec![]), - T("bytes= , , , ", 10, vec![]), - T( - "bytes=0-9", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=0-", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=5-", - 10, - vec![HttpRange { - start: 5, - length: 5, - }], - ), - T( - "bytes=0-20", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=15-,0-5", - 10, - vec![HttpRange { - start: 0, - length: 6, - }], - ), - T( - "bytes=1-2,5-", - 10, - vec![ - HttpRange { - start: 1, - length: 2, - }, - HttpRange { - start: 5, - length: 5, - }, - ], - ), - T( - "bytes=-2 , 7-", - 11, - vec![ - HttpRange { - start: 9, - length: 2, - }, - HttpRange { - start: 7, - length: 4, - }, - ], - ), - T( - "bytes=0-0 ,2-2, 7-", - 11, - vec![ - HttpRange { - start: 0, - length: 1, - }, - HttpRange { - start: 2, - length: 1, - }, - HttpRange { - start: 7, - length: 4, - }, - ], - ), - T( - "bytes=-5", - 10, - vec![HttpRange { - start: 5, - length: 5, - }], - ), - T( - "bytes=-15", - 10, - vec![HttpRange { - start: 0, - length: 10, - }], - ), - T( - "bytes=0-499", - 10000, - vec![HttpRange { - start: 0, - length: 500, - }], - ), - T( - "bytes=500-999", - 10000, - vec![HttpRange { - start: 500, - length: 500, - }], - ), - T( - "bytes=-500", - 10000, - vec![HttpRange { - start: 9500, - length: 500, - }], - ), - T( - "bytes=9500-", - 10000, - vec![HttpRange { - start: 9500, - length: 500, - }], - ), - T( - "bytes=0-0,-1", - 10000, - vec![ - HttpRange { - start: 0, - length: 1, - }, - HttpRange { - start: 9999, - length: 1, - }, - ], - ), - T( - "bytes=500-600,601-999", - 10000, - vec![ - HttpRange { - start: 500, - length: 101, - }, - HttpRange { - start: 601, - length: 399, - }, - ], - ), - T( - "bytes=500-700,601-999", - 10000, - vec![ - HttpRange { - start: 500, - length: 201, - }, - HttpRange { - start: 601, - length: 399, - }, - ], - ), - // Match Apache laxity: - T( - "bytes= 1 -2 , 4- 5, 7 - 8 , ,,", - 11, - vec![ - HttpRange { - start: 1, - length: 2, - }, - HttpRange { - start: 4, - length: 2, - }, - HttpRange { - start: 7, - length: 2, - }, - ], - ), - ]; - - for t in tests { - let header = t.0; - let size = t.1; - let expected = t.2; - - let res = HttpRange::parse(header, size); - - if res.is_err() { - if expected.is_empty() { - continue; - } else { - assert!( - false, - "parse({}, {}) returned error {:?}", - header, - size, - res.unwrap_err() - ); - } - } - - let got = res.unwrap(); - - if got.len() != expected.len() { - assert!( - false, - "len(parseRange({}, {})) = {}, want {}", - header, - size, - got.len(), - expected.len() - ); - continue; - } - - for i in 0..expected.len() { - if got[i].start != expected[i].start { - assert!( - false, - "parseRange({}, {})[{}].start = {}, want {}", - header, size, i, got[i].start, expected[i].start - ) - } - if got[i].length != expected[i].length { - assert!( - false, - "parseRange({}, {})[{}].length = {}, want {}", - header, size, i, got[i].length, expected[i].length - ) - } - } - } - } -} diff --git a/actix-files/tests/test space.binary b/actix-files/tests/test space.binary deleted file mode 100644 index ef8ff0245..000000000 --- a/actix-files/tests/test space.binary +++ /dev/null @@ -1 +0,0 @@ -ÂTÇ‘É‚Vù2þvI ª–\ÇRË™–ˆæeÞvDØ:è—½¬RVÖYpíÿ;ÍÏGñùp!2÷CŒ.– û®õpA !ûߦÙx j+Uc÷±©X”c%Û;ï"yì­AI \ No newline at end of file diff --git a/actix-files/tests/test.binary b/actix-files/tests/test.binary deleted file mode 100644 index ef8ff0245..000000000 --- a/actix-files/tests/test.binary +++ /dev/null @@ -1 +0,0 @@ -ÂTÇ‘É‚Vù2þvI ª–\ÇRË™–ˆæeÞvDØ:è—½¬RVÖYpíÿ;ÍÏGñùp!2÷CŒ.– û®õpA !ûߦÙx j+Uc÷±©X”c%Û;ï"yì­AI \ No newline at end of file diff --git a/actix-files/tests/test.png b/actix-files/tests/test.png deleted file mode 100644 index 6b7cdc0b8..000000000 Binary files a/actix-files/tests/test.png and /dev/null differ 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 deleted file mode 100644 index 1714b3640..000000000 --- a/actix-framed/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Framed app for actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-framed)](https://crates.io/crates/actix-framed) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -## 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 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> + 'static, - - E: fmt::Debug, - { - FramedRoute { - handler, - pattern: self.pattern, - methods: self.methods, - state: PhantomData, - } - } -} - -impl HttpServiceFactory for FramedRoute -where - Io: AsyncRead + AsyncWrite + 'static, - F: FnMut(FramedRequest) -> R + Clone, - R: Future> + 'static, - E: fmt::Display, -{ - type Factory = FramedRouteFactory; - - fn path(&self) -> &str { - &self.pattern - } - - fn create(self) -> Self::Factory { - FramedRouteFactory { - handler: self.handler, - methods: self.methods, - _t: PhantomData, - } - } -} - -pub struct FramedRouteFactory { - handler: F, - methods: Vec, - _t: PhantomData<(Io, S, R, E)>, -} - -impl ServiceFactory for FramedRouteFactory -where - Io: AsyncRead + AsyncWrite + 'static, - F: FnMut(FramedRequest) -> R + Clone, - R: Future> + 'static, - E: fmt::Display, -{ - type Config = (); - type Request = FramedRequest; - type Response = (); - type Error = Error; - type InitError = (); - type Service = FramedRouteService; - type Future = Ready>; - - fn new_service(&self, _: ()) -> Self::Future { - ok(FramedRouteService { - handler: self.handler.clone(), - methods: self.methods.clone(), - _t: PhantomData, - }) - } -} - -pub struct FramedRouteService { - handler: F, - methods: Vec, - _t: PhantomData<(Io, S, R, E)>, -} - -impl Service for FramedRouteService -where - Io: AsyncRead + AsyncWrite + 'static, - F: FnMut(FramedRequest) -> R + Clone, - R: Future> + 'static, - E: fmt::Display, -{ - type Request = FramedRequest; - type Response = (); - type Error = Error; - type Future = LocalBoxFuture<'static, Result<(), Error>>; - - fn poll_ready(&mut self, _: &mut Context) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: FramedRequest) -> Self::Future { - let fut = (self.handler)(req); - - async move { - let res = fut.await; - if let Err(e) = res { - error!("Error in request handler: {}", e); - } - Ok(()) - } - .boxed_local() - } -} diff --git a/actix-framed/src/service.rs b/actix-framed/src/service.rs deleted file mode 100644 index 92393ca75..000000000 --- a/actix-framed/src/service.rs +++ /dev/null @@ -1,156 +0,0 @@ -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_http::body::BodySize; -use actix_http::error::ResponseError; -use actix_http::h1::{Codec, Message}; -use actix_http::ws::{verify_handshake, HandshakeError}; -use actix_http::{Request, Response}; -use actix_service::{Service, ServiceFactory}; -use futures::future::{err, ok, Either, Ready}; -use futures::Future; - -/// Service that verifies incoming request if it is valid websocket -/// upgrade request. In case of error returns `HandshakeError` -pub struct VerifyWebSockets { - _t: PhantomData<(T, C)>, -} - -impl Default for VerifyWebSockets { - fn default() -> Self { - VerifyWebSockets { _t: PhantomData } - } -} - -impl ServiceFactory for VerifyWebSockets { - type Config = C; - type Request = (Request, Framed); - type Response = (Request, Framed); - type Error = (HandshakeError, Framed); - type InitError = (); - type Service = VerifyWebSockets; - type Future = Ready>; - - fn new_service(&self, _: C) -> Self::Future { - ok(VerifyWebSockets { _t: PhantomData }) - } -} - -impl Service for VerifyWebSockets { - type Request = (Request, Framed); - type Response = (Request, Framed); - type Error = (HandshakeError, Framed); - type Future = Ready>; - - fn poll_ready(&mut self, _: &mut Context) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future { - match verify_handshake(req.head()) { - Err(e) => err((e, framed)), - Ok(_) => ok((req, framed)), - } - } -} - -/// Send http/1 error response -pub struct SendError(PhantomData<(T, R, E, C)>); - -impl Default for SendError -where - T: AsyncRead + AsyncWrite, - E: ResponseError, -{ - fn default() -> Self { - SendError(PhantomData) - } -} - -impl ServiceFactory for SendError -where - T: AsyncRead + AsyncWrite + Unpin + 'static, - R: 'static, - E: ResponseError + 'static, -{ - type Config = C; - type Request = Result)>; - type Response = R; - type Error = (E, Framed); - type InitError = (); - type Service = SendError; - type Future = Ready>; - - fn new_service(&self, _: C) -> Self::Future { - ok(SendError(PhantomData)) - } -} - -impl Service for SendError -where - T: AsyncRead + AsyncWrite + Unpin + 'static, - R: 'static, - E: ResponseError + 'static, -{ - type Request = Result)>; - type Response = R; - type Error = (E, Framed); - type Future = Either)>>, SendErrorFut>; - - fn poll_ready(&mut self, _: &mut Context) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Result)>) -> Self::Future { - match req { - Ok(r) => Either::Left(ok(r)), - Err((e, framed)) => { - let res = e.error_response().drop_body(); - Either::Right(SendErrorFut { - framed: Some(framed), - res: Some((res, BodySize::Empty).into()), - err: Some(e), - _t: PhantomData, - }) - } - } - } -} - -#[pin_project::pin_project] -pub struct SendErrorFut { - res: Option, BodySize)>>, - framed: Option>, - err: Option, - _t: PhantomData, -} - -impl Future for SendErrorFut -where - E: ResponseError, - T: AsyncRead + AsyncWrite + Unpin, -{ - type Output = Result)>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { - if let Some(res) = self.res.take() { - if self.framed.as_mut().unwrap().write(res).is_err() { - return Poll::Ready(Err(( - self.err.take().unwrap(), - self.framed.take().unwrap(), - ))); - } - } - match self.framed.as_mut().unwrap().flush(cx) { - Poll::Ready(Ok(_)) => { - Poll::Ready(Err((self.err.take().unwrap(), self.framed.take().unwrap()))) - } - Poll::Ready(Err(_)) => { - Poll::Ready(Err((self.err.take().unwrap(), self.framed.take().unwrap()))) - } - Poll::Pending => Poll::Pending, - } - } -} diff --git a/actix-framed/src/state.rs b/actix-framed/src/state.rs deleted file mode 100644 index 600a639ca..000000000 --- a/actix-framed/src/state.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::ops::Deref; -use std::sync::Arc; - -/// Application state -pub struct State(Arc); - -impl State { - pub fn new(state: S) -> State { - State(Arc::new(state)) - } - - pub fn get_ref(&self) -> &S { - self.0.as_ref() - } -} - -impl Deref for State { - type Target = S; - - fn deref(&self) -> &S { - self.0.as_ref() - } -} - -impl Clone for State { - fn clone(&self) -> State { - State(self.0.clone()) - } -} diff --git a/actix-framed/src/test.rs b/actix-framed/src/test.rs deleted file mode 100644 index b8029531e..000000000 --- a/actix-framed/src/test.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Various helpers for Actix applications to use during testing. -use std::convert::TryFrom; -use std::future::Future; - -use actix_codec::Framed; -use actix_http::h1::Codec; -use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; -use actix_http::http::{Error as HttpError, Method, Uri, Version}; -use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest}; -use actix_router::{Path, Url}; - -use crate::{FramedRequest, State}; - -/// Test `Request` builder. -pub struct TestRequest { - req: HttpTestRequest, - path: Path, - state: State, -} - -impl Default for TestRequest<()> { - fn default() -> TestRequest { - TestRequest { - req: HttpTestRequest::default(), - path: Path::new(Url::new(Uri::default())), - state: State::new(()), - } - } -} - -impl TestRequest<()> { - /// Create TestRequest and set request uri - pub fn with_uri(path: &str) -> Self { - Self::get().uri(path) - } - - /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> Self { - Self::default().set(hdr) - } - - /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - Self::default().header(key, value) - } - - /// Create TestRequest and set method to `Method::GET` - pub fn get() -> Self { - Self::default().method(Method::GET) - } - - /// Create TestRequest and set method to `Method::POST` - pub fn post() -> Self { - Self::default().method(Method::POST) - } -} - -impl TestRequest { - /// Create TestRequest and set request uri - pub fn with_state(state: S) -> TestRequest { - let req = TestRequest::get(); - TestRequest { - state: State::new(state), - req: req.req, - path: req.path, - } - } - - /// Set HTTP version of this request - pub fn version(mut self, ver: Version) -> Self { - self.req.version(ver); - self - } - - /// Set HTTP method of this request - pub fn method(mut self, meth: Method) -> Self { - self.req.method(meth); - self - } - - /// Set HTTP Uri of this request - pub fn uri(mut self, path: &str) -> Self { - self.req.uri(path); - self - } - - /// Set a header - pub fn set(mut self, hdr: H) -> Self { - self.req.set(hdr); - self - } - - /// Set a header - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - self.req.header(key, value); - self - } - - /// Set request path pattern parameter - pub fn param(mut self, name: &'static str, value: &'static str) -> Self { - self.path.add_static(name, value); - self - } - - /// Complete request creation and generate `Request` instance - pub fn finish(mut self) -> FramedRequest { - let req = self.req.finish(); - self.path.get_mut().update(req.uri()); - let framed = Framed::new(TestBuffer::empty(), Codec::default()); - FramedRequest::new(req, framed, self.path, self.state) - } - - /// This method generates `FramedRequest` instance and executes async handler - pub async fn run(self, f: F) -> Result - where - F: FnOnce(FramedRequest) -> R, - R: Future>, - { - f(self.finish()).await - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test() { - let req = TestRequest::with_uri("/index.html") - .header("x-test", "test") - .param("test", "123") - .finish(); - - assert_eq!(*req.state(), ()); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(req.method(), Method::GET); - assert_eq!(req.path(), "/index.html"); - assert_eq!(req.query_string(), ""); - assert_eq!( - req.headers().get("x-test").unwrap().to_str().unwrap(), - "test" - ); - assert_eq!(&req.match_info()["test"], "123"); - } -} diff --git a/actix-framed/tests/test_server.rs b/actix-framed/tests/test_server.rs deleted file mode 100644 index 7d6fc08a6..000000000 --- a/actix-framed/tests/test_server.rs +++ /dev/null @@ -1,159 +0,0 @@ -use actix_codec::{AsyncRead, AsyncWrite}; -use actix_http::{body, http::StatusCode, ws, Error, HttpService, Response}; -use actix_http_test::test_server; -use actix_service::{pipeline_factory, IntoServiceFactory, ServiceFactory}; -use actix_utils::framed::Dispatcher; -use bytes::Bytes; -use futures::{future, SinkExt, StreamExt}; - -use actix_framed::{FramedApp, FramedRequest, FramedRoute, SendError, VerifyWebSockets}; - -async fn ws_service( - req: FramedRequest, -) -> Result<(), Error> { - let (req, mut framed, _) = req.into_parts(); - let res = ws::handshake(req.head()).unwrap().message_body(()); - - framed - .send((res, body::BodySize::None).into()) - .await - .unwrap(); - Dispatcher::new(framed.into_framed(ws::Codec::new()), service) - .await - .unwrap(); - - Ok(()) -} - -async fn service(msg: ws::Frame) -> Result { - let msg = match msg { - ws::Frame::Ping(msg) => ws::Message::Pong(msg), - ws::Frame::Text(text) => { - ws::Message::Text(String::from_utf8_lossy(&text).to_string()) - } - ws::Frame::Binary(bin) => ws::Message::Binary(bin), - ws::Frame::Close(reason) => ws::Message::Close(reason), - _ => panic!(), - }; - Ok(msg) -} - -#[actix_rt::test] -async fn test_simple() { - let mut srv = test_server(|| { - HttpService::build() - .upgrade( - FramedApp::new().service(FramedRoute::get("/index.html").to(ws_service)), - ) - .finish(|_| future::ok::<_, Error>(Response::NotFound())) - .tcp() - }); - - assert!(srv.ws_at("/test").await.is_err()); - - // client service - let mut framed = srv.ws_at("/index.html").await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Text(Bytes::from_static(b"text")) - ); - - framed - .send(ws::Message::Binary("text".into())) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Binary(Bytes::from_static(b"text")) - ); - - framed.send(ws::Message::Ping("text".into())).await.unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Pong("text".to_string().into()) - ); - - framed - .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - .await - .unwrap(); - - let (item, _) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Close(Some(ws::CloseCode::Normal.into())) - ); -} - -#[actix_rt::test] -async fn test_service() { - let mut srv = test_server(|| { - pipeline_factory(actix_http::h1::OneRequest::new().map_err(|_| ())).and_then( - pipeline_factory( - pipeline_factory(VerifyWebSockets::default()) - .then(SendError::default()) - .map_err(|_| ()), - ) - .and_then( - FramedApp::new() - .service(FramedRoute::get("/index.html").to(ws_service)) - .into_factory() - .map_err(|_| ()), - ), - ) - }); - - // non ws request - let res = srv.get("/index.html").send().await.unwrap(); - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - - // not found - assert!(srv.ws_at("/test").await.is_err()); - - // client service - let mut framed = srv.ws_at("/index.html").await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Text(Bytes::from_static(b"text")) - ); - - framed - .send(ws::Message::Binary("text".into())) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Binary(Bytes::from_static(b"text")) - ); - - framed.send(ws::Message::Ping("text".into())).await.unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Pong("text".to_string().into()) - ); - - framed - .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - .await - .unwrap(); - - let (item, _) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Close(Some(ws::CloseCode::Normal.into())) - ); -} diff --git a/actix-http/.appveyor.yml b/actix-http/.appveyor.yml deleted file mode 100644 index 780fdd6b5..000000000 --- a/actix-http/.appveyor.yml +++ /dev/null @@ -1,41 +0,0 @@ -environment: - global: - PROJECT_NAME: actix-http - 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 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md deleted file mode 100644 index 511ef4f1c..000000000 --- a/actix-http/CHANGES.md +++ /dev/null @@ -1,318 +0,0 @@ -# Changes - -# [Unreleased] - -### Changed - -* Update the `time` dependency to 0.2.5 - -### Fixed - -* Allow `SameSite=None` cookies to be sent in a response. - -## [1.0.1] - 2019-12-20 - -### Fixed - -* Poll upgrade service's readiness from HTTP service handlers - -* Replace brotli with brotli2 #1224 - -## [1.0.0] - 2019-12-13 - -### Added - -* Add websockets continuation frame support - -### Changed - -* Replace `flate2-xxx` features with `compress` - -## [1.0.0-alpha.5] - 2019-12-09 - -### Fixed - -* Check `Upgrade` service readiness before calling it - -* Fix buffer remaining capacity calcualtion - -### Changed - -* Websockets: Ping and Pong should have binary data #1049 - -## [1.0.0-alpha.4] - 2019-12-08 - -### Added - -* Add impl ResponseBuilder for Error - -### Changed - -* Use rust based brotli compression library - -## [1.0.0-alpha.3] - 2019-12-07 - -### Changed - -* Migrate to tokio 0.2 - -* Migrate to `std::future` - - -## [0.2.11] - 2019-11-06 - -### Added - -* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() - -* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) - -* Allow to use `std::convert::Infallible` as `actix_http::error::Error` - -### Fixed - -* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header #1118 - - -## [0.2.10] - 2019-09-11 - -### Added - -* Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` - -### Fixed - -* h2 will use error response #1080 - -* on_connect result isn't added to request extensions for http2 requests #1009 - - -## [0.2.9] - 2019-08-13 - -### Changed - -* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation - -* Update percent-encoding to 2.1 - -* Update serde_urlencoded to 0.6.1 - -### Fixed - -* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) - - -## [0.2.8] - 2019-08-01 - -### Added - -* Add `rustls` support - -* Add `Clone` impl for `HeaderMap` - -### Fixed - -* awc client panic #1016 - -* Invalid response with compression middleware enabled, but compression-related features disabled #997 - - -## [0.2.7] - 2019-07-18 - -### Added - -* Add support for downcasting response errors #986 - - -## [0.2.6] - 2019-07-17 - -### Changed - -* Replace `ClonableService` with local copy - -* Upgrade `rand` dependency version to 0.7 - - -## [0.2.5] - 2019-06-28 - -### Added - -* Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 - -### Changed - -* Use `encoding_rs` crate instead of unmaintained `encoding` crate - -* Add `Copy` and `Clone` impls for `ws::Codec` - - -## [0.2.4] - 2019-06-16 - -### Fixed - -* Do not compress NoContent (204) responses #918 - - -## [0.2.3] - 2019-06-02 - -### Added - -* Debug impl for ResponseBuilder - -* From SizedStream and BodyStream for Body - -### Changed - -* SizedStream uses u64 - - -## [0.2.2] - 2019-05-29 - -### Fixed - -* Parse incoming stream before closing stream on disconnect #868 - - -## [0.2.1] - 2019-05-25 - -### Fixed - -* Handle socket read disconnect - - -## [0.2.0] - 2019-05-12 - -### Changed - -* Update actix-service to 0.4 - -* Expect and upgrade services accept `ServerConfig` config. - -### Deleted - -* `OneRequest` service - - -## [0.1.5] - 2019-05-04 - -### Fixed - -* Clean up response extensions in response pool #817 - - -## [0.1.4] - 2019-04-24 - -### Added - -* Allow to render h1 request headers in `Camel-Case` - -### Fixed - -* Read until eof for http/1.0 responses #771 - - -## [0.1.3] - 2019-04-23 - -### Fixed - -* Fix http client pool management - -* Fix http client wait queue management #794 - - -## [0.1.2] - 2019-04-23 - -### Fixed - -* Fix BorrowMutError panic in client connector #793 - - -## [0.1.1] - 2019-04-19 - -### Changed - -* Cookie::max_age() accepts value in seconds - -* Cookie::max_age_time() accepts value in time::Duration - -* Allow to specify server address for client connector - - -## [0.1.0] - 2019-04-16 - -### Added - -* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` - -### Changed - -* `actix_http::encoding` always available - -* use trust-dns-resolver 0.11.0 - - -## [0.1.0-alpha.5] - 2019-04-12 - -### Added - -* Allow to use custom service for upgrade requests - -* Added `h1::SendResponse` future. - -### Changed - -* MessageBody::length() renamed to MessageBody::size() for consistency - -* ws handshake verification functions take RequestHead instead of Request - - -## [0.1.0-alpha.4] - 2019-04-08 - -### Added - -* Allow to use custom `Expect` handler - -* Add minimal `std::error::Error` impl for `Error` - -### Changed - -* Export IntoHeaderValue - -* Render error and return as response body - -* Use thread pool for response body comression - -### Deleted - -* Removed PayloadBuffer - - -## [0.1.0-alpha.3] - 2019-04-02 - -### Added - -* Warn when an unsealed private cookie isn't valid UTF-8 - -### Fixed - -* Rust 1.31.0 compatibility - -* Preallocate read buffer for h1 codec - -* Detect socket disconnection during protocol selection - - -## [0.1.0-alpha.2] - 2019-03-29 - -### Added - -* Added ws::Message::Nop, no-op websockets message - -### Changed - -* Do not use thread pool for decomression if chunk size is smaller than 2048. - - -## [0.1.0-alpha.1] - 2019-03-28 - -* Initial impl diff --git a/actix-http/CODE_OF_CONDUCT.md b/actix-http/CODE_OF_CONDUCT.md deleted file mode 100644 index 599b28c0d..000000000 --- a/actix-http/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml deleted file mode 100644 index cd813e49f..000000000 --- a/actix-http/Cargo.toml +++ /dev/null @@ -1,100 +0,0 @@ -[package] -name = "actix-http" -version = "1.0.1" -authors = ["Nikolay Kim "] -description = "Actix http primitives" -readme = "README.md" -keywords = ["actix", "http", "framework", "async", "futures"] -homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-http/" -categories = ["network-programming", "asynchronous", - "web-programming::http-server", - "web-programming::websocket"] -license = "MIT/Apache-2.0" -edition = "2018" - -[package.metadata.docs.rs] -features = ["openssl", "rustls", "failure", "compress", "secure-cookies"] - -[lib] -name = "actix_http" -path = "src/lib.rs" - -[features] -default = [] - -# openssl -openssl = ["actix-tls/openssl", "actix-connect/openssl"] - -# rustls support -rustls = ["actix-tls/rustls", "actix-connect/rustls"] - -# enable compressison support -compress = ["flate2", "brotli2"] - -# failure integration. actix does not use failure anymore -failure = ["fail-ure"] - -# support for secure cookies -secure-cookies = ["ring"] - -[dependencies] -actix-service = "1.0.1" -actix-codec = "0.2.0" -actix-connect = "1.0.1" -actix-utils = "1.0.3" -actix-rt = "1.0.0" -actix-threadpool = "0.3.1" -actix-tls = { version = "1.0.0", optional = true } - -base64 = "0.11" -bitflags = "1.2" -bytes = "0.5.3" -copyless = "0.1.4" -derive_more = "0.99.2" -either = "1.5.3" -encoding_rs = "0.8" -futures-core = "0.3.1" -futures-util = "0.3.1" -futures-channel = "0.3.1" -fxhash = "0.2.1" -h2 = "0.2.1" -http = "0.2.0" -httparse = "1.3" -indexmap = "1.3" -lazy_static = "1.4" -language-tags = "0.2" -log = "0.4" -mime = "0.3" -percent-encoding = "2.1" -pin-project = "0.4.6" -rand = "0.7" -regex = "1.3" -serde = "1.0" -serde_json = "1.0" -sha-1 = "0.8" -slab = "0.4" -serde_urlencoded = "0.6.1" -time = { version = "0.2.5", default-features = false, features = ["std"] } - -# for secure cookie -ring = { version = "0.16.9", optional = true } - -# compression -brotli2 = { version="0.3.2", optional = true } -flate2 = { version = "1.0.13", optional = true } - -# optional deps -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-http-test = { version = "1.0.0", features=["openssl"] } -actix-tls = { version = "1.0.0", features=["openssl"] } -futures = "0.3.1" -env_logger = "0.6" -serde_derive = "1.0" -open-ssl = { version="0.10", package = "openssl" } -rust-tls = { version="0.16", package = "rustls" } diff --git a/actix-http/LICENSE-APACHE b/actix-http/LICENSE-APACHE deleted file mode 100644 index 6cdf2d16c..000000000 --- a/actix-http/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-http/LICENSE-MIT b/actix-http/LICENSE-MIT deleted file mode 100644 index 0f80296ae..000000000 --- a/actix-http/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-http/README.md b/actix-http/README.md deleted file mode 100644 index d75e822ba..000000000 --- a/actix-http/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Actix http [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-http)](https://crates.io/crates/actix-http) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -Actix http - -## Documentation & community resources - -* [User Guide](https://actix.rs/docs/) -* [API Documentation](https://docs.rs/actix-http/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-http](https://crates.io/crates/actix-http) -* Minimum supported Rust version: 1.31 or later - -## Example - -```rust -// see examples/framed_hello.rs for complete list of used crates. -extern crate actix_http; -use actix_http::{h1, Response, ServiceConfig}; - -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(); -} -``` - -## License - -This project is licensed under either of - -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) - -at your option. - -## Code of Conduct - -Contribution to the actix-http crate is organized under the terms of the -Contributor Covenant, the maintainer of actix-http, @fafhrd91, promises to -intervene to uphold that code of conduct. diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs deleted file mode 100644 index 3d57a472a..000000000 --- a/actix-http/examples/echo.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::{env, io}; - -use actix_http::{Error, HttpService, Request, Response}; -use actix_server::Server; -use bytes::BytesMut; -use futures::StreamExt; -use http::header::HeaderValue; -use log::info; - -#[actix_rt::main] -async fn main() -> io::Result<()> { - env::set_var("RUST_LOG", "echo=info"); - env_logger::init(); - - Server::build() - .bind("echo", "127.0.0.1:8080", || { - 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), - ) - } - }) - .tcp() - })? - .run() - .await -} diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs deleted file mode 100644 index f89ea2dfb..000000000 --- a/actix-http/examples/echo2.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::{env, io}; - -use actix_http::http::HeaderValue; -use actix_http::{Error, HttpService, Request, Response}; -use actix_server::Server; -use bytes::BytesMut; -use futures::StreamExt; -use log::info; - -async fn handle_request(mut req: Request) -> Result { - let mut body = BytesMut::new(); - while let Some(item) = req.payload().next().await { - body.extend_from_slice(&item?) - } - - info!("request body: {:?}", body); - Ok(Response::Ok() - .header("x-head", HeaderValue::from_static("dummy value!")) - .body(body)) -} - -#[actix_rt::main] -async fn main() -> io::Result<()> { - env::set_var("RUST_LOG", "echo=info"); - env_logger::init(); - - Server::build() - .bind("echo", "127.0.0.1:8080", || { - HttpService::build().finish(handle_request).tcp() - })? - .run() - .await -} diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs deleted file mode 100644 index 4134ee734..000000000 --- a/actix-http/examples/hello-world.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::{env, io}; - -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/rustfmt.toml b/actix-http/rustfmt.toml deleted file mode 100644 index 5fcaaca0f..000000000 --- a/actix-http/rustfmt.toml +++ /dev/null @@ -1,5 +0,0 @@ -max_width = 89 -reorder_imports = true -#wrap_comments = true -#fn_args_density = "Compressed" -#use_small_heuristics = false diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs deleted file mode 100644 index 881764439..000000000 --- a/actix-http/src/body.rs +++ /dev/null @@ -1,650 +0,0 @@ -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; -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; - -#[derive(Debug, PartialEq, Copy, Clone)] -/// Body size hint -pub enum BodySize { - None, - Empty, - Sized(usize), - Sized64(u64), - Stream, -} - -impl BodySize { - pub fn is_eof(&self) -> bool { - match self { - BodySize::None - | BodySize::Empty - | BodySize::Sized(0) - | BodySize::Sized64(0) => true, - _ => false, - } - } -} - -/// Type that provides this trait can be streamed to a peer. -pub trait MessageBody { - fn size(&self) -> BodySize; - - fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll>>; -} - -impl MessageBody for () { - fn size(&self) -> BodySize { - BodySize::Empty - } - - fn poll_next(&mut self, _: &mut Context<'_>) -> Poll>> { - Poll::Ready(None) - } -} - -impl MessageBody for Box { - fn size(&self) -> BodySize { - self.as_ref().size() - } - - fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll>> { - self.as_mut().poll_next(cx) - } -} - -#[pin_project] -pub enum ResponseBody { - Body(B), - Other(Body), -} - -impl ResponseBody { - pub fn into_body(self) -> ResponseBody { - match self { - ResponseBody::Body(b) => ResponseBody::Other(b), - ResponseBody::Other(b) => ResponseBody::Other(b), - } - } -} - -impl ResponseBody { - pub fn take_body(&mut self) -> ResponseBody { - std::mem::replace(self, ResponseBody::Other(Body::None)) - } -} - -impl ResponseBody { - pub fn as_ref(&self) -> Option<&B> { - if let ResponseBody::Body(ref b) = self { - Some(b) - } else { - None - } - } -} - -impl MessageBody for ResponseBody { - fn size(&self) -> BodySize { - match self { - ResponseBody::Body(ref body) => body.size(), - ResponseBody::Other(ref body) => body.size(), - } - } - - fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll>> { - match self { - ResponseBody::Body(ref mut body) => body.poll_next(cx), - ResponseBody::Other(ref mut body) => body.poll_next(cx), - } - } -} - -impl Stream for ResponseBody { - type Item = Result; - - #[project] - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - #[project] - match self.project() { - ResponseBody::Body(ref mut body) => body.poll_next(cx), - ResponseBody::Other(ref mut body) => body.poll_next(cx), - } - } -} - -/// Represents various types of http message body. -pub enum Body { - /// Empty response. `Content-Length` header is not set. - None, - /// Zero sized response body. `Content-Length` header is set to `0`. - Empty, - /// Specific response body. - Bytes(Bytes), - /// Generic message body. - Message(Box), -} - -impl Body { - /// Create body from slice (copy) - pub fn from_slice(s: &[u8]) -> Body { - Body::Bytes(Bytes::copy_from_slice(s)) - } - - /// Create body from generic message body. - pub fn from_message(body: B) -> Body { - Body::Message(Box::new(body)) - } -} - -impl MessageBody for Body { - fn size(&self) -> BodySize { - match self { - Body::None => BodySize::None, - Body::Empty => BodySize::Empty, - Body::Bytes(ref bin) => BodySize::Sized(bin.len()), - Body::Message(ref body) => body.size(), - } - } - - fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll>> { - match self { - Body::None => Poll::Ready(None), - Body::Empty => Poll::Ready(None), - Body::Bytes(ref mut bin) => { - let len = bin.len(); - if len == 0 { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(mem::replace(bin, Bytes::new())))) - } - } - Body::Message(ref mut body) => body.poll_next(cx), - } - } -} - -impl PartialEq for Body { - fn eq(&self, other: &Body) -> bool { - match *self { - Body::None => match *other { - Body::None => true, - _ => false, - }, - Body::Empty => match *other { - Body::Empty => true, - _ => false, - }, - Body::Bytes(ref b) => match *other { - Body::Bytes(ref b2) => b == b2, - _ => false, - }, - Body::Message(_) => false, - } - } -} - -impl fmt::Debug for Body { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Body::None => write!(f, "Body::None"), - Body::Empty => write!(f, "Body::Empty"), - Body::Bytes(ref b) => write!(f, "Body::Bytes({:?})", b), - Body::Message(_) => write!(f, "Body::Message(_)"), - } - } -} - -impl From<&'static str> for Body { - fn from(s: &'static str) -> Body { - Body::Bytes(Bytes::from_static(s.as_ref())) - } -} - -impl From<&'static [u8]> for Body { - fn from(s: &'static [u8]) -> Body { - Body::Bytes(Bytes::from_static(s)) - } -} - -impl From> for Body { - fn from(vec: Vec) -> Body { - Body::Bytes(Bytes::from(vec)) - } -} - -impl From for Body { - fn from(s: String) -> Body { - s.into_bytes().into() - } -} - -impl<'a> From<&'a String> for Body { - fn from(s: &'a String) -> Body { - Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&s))) - } -} - -impl From for Body { - fn from(s: Bytes) -> Body { - Body::Bytes(s) - } -} - -impl From for Body { - fn from(s: BytesMut) -> Body { - Body::Bytes(s.freeze()) - } -} - -impl From for Body { - fn from(v: serde_json::Value) -> Body { - Body::Bytes(v.to_string().into()) - } -} - -impl From> for Body -where - S: Stream> + 'static, -{ - fn from(s: SizedStream) -> Body { - Body::from_message(s) - } -} - -impl From> for Body -where - S: Stream> + 'static, - E: Into + 'static, -{ - fn from(s: BodyStream) -> Body { - Body::from_message(s) - } -} - -impl MessageBody for Bytes { - fn size(&self) -> BodySize { - BodySize::Sized(self.len()) - } - - fn poll_next(&mut self, _: &mut Context<'_>) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(mem::replace(self, Bytes::new())))) - } - } -} - -impl MessageBody for BytesMut { - fn size(&self) -> BodySize { - BodySize::Sized(self.len()) - } - - fn poll_next(&mut self, _: &mut Context<'_>) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(mem::replace(self, BytesMut::new()).freeze()))) - } - } -} - -impl MessageBody for &'static str { - fn size(&self) -> BodySize { - BodySize::Sized(self.len()) - } - - fn poll_next(&mut self, _: &mut Context<'_>) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::from_static( - mem::replace(self, "").as_ref(), - )))) - } - } -} - -impl MessageBody for &'static [u8] { - fn size(&self) -> BodySize { - BodySize::Sized(self.len()) - } - - fn poll_next(&mut self, _: &mut Context<'_>) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::from_static(mem::replace(self, b""))))) - } - } -} - -impl MessageBody for Vec { - fn size(&self) -> BodySize { - BodySize::Sized(self.len()) - } - - fn poll_next(&mut self, _: &mut Context<'_>) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::from(mem::replace(self, Vec::new()))))) - } - } -} - -impl MessageBody for String { - fn size(&self) -> BodySize { - BodySize::Sized(self.len()) - } - - fn poll_next(&mut self, _: &mut Context<'_>) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::from( - mem::replace(self, String::new()).into_bytes(), - )))) - } - } -} - -/// Type represent streaming body. -/// Response does not contain `content-length` header and appropriate transfer encoding is used. -#[pin_project] -pub struct BodyStream { - #[pin] - stream: S, - _t: PhantomData, -} - -impl BodyStream -where - S: Stream>, - E: Into, -{ - pub fn new(stream: S) -> Self { - BodyStream { - stream, - _t: PhantomData, - } - } -} - -impl MessageBody for BodyStream -where - S: Stream>, - E: Into, -{ - fn size(&self) -> BodySize { - BodySize::Stream - } - - /// Attempts to pull out the next value of the underlying [`Stream`]. - /// - /// Empty values are skipped to prevent [`BodyStream`]'s transmission being - /// ended on a zero-length chunk, but rather proceed until the underlying - /// [`Stream`] ends. - fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll>> { - let mut stream = unsafe { Pin::new_unchecked(self) }.project().stream; - loop { - return Poll::Ready(match ready!(stream.as_mut().poll_next(cx)) { - Some(Ok(ref bytes)) if bytes.is_empty() => continue, - opt => opt.map(|res| res.map_err(Into::into)), - }); - } - } -} - -/// Type represent streaming body. This body implementation should be used -/// if total size of stream is known. Data get sent as is without using transfer encoding. -#[pin_project] -pub struct SizedStream { - size: u64, - #[pin] - stream: S, -} - -impl SizedStream -where - S: Stream>, -{ - pub fn new(size: u64, stream: S) -> Self { - SizedStream { size, stream } - } -} - -impl MessageBody for SizedStream -where - S: Stream>, -{ - fn size(&self) -> BodySize { - BodySize::Sized64(self.size) - } - - /// Attempts to pull out the next value of the underlying [`Stream`]. - /// - /// Empty values are skipped to prevent [`SizedStream`]'s transmission being - /// ended on a zero-length chunk, but rather proceed until the underlying - /// [`Stream`] ends. - fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll>> { - let mut stream = unsafe { Pin::new_unchecked(self) }.project().stream; - loop { - return Poll::Ready(match ready!(stream.as_mut().poll_next(cx)) { - Some(Ok(ref bytes)) if bytes.is_empty() => continue, - val => val, - }); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use futures::stream; - use futures_util::future::poll_fn; - - impl Body { - pub(crate) fn get_ref(&self) -> &[u8] { - match *self { - Body::Bytes(ref bin) => &bin, - _ => panic!(), - } - } - } - - impl ResponseBody { - pub(crate) fn get_ref(&self) -> &[u8] { - match *self { - ResponseBody::Body(ref b) => b.get_ref(), - ResponseBody::Other(ref b) => b.get_ref(), - } - } - } - - #[actix_rt::test] - async fn test_static_str() { - assert_eq!(Body::from("").size(), BodySize::Sized(0)); - assert_eq!(Body::from("test").size(), BodySize::Sized(4)); - assert_eq!(Body::from("test").get_ref(), b"test"); - - assert_eq!("test".size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| "test".poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_static_bytes() { - assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); - assert_eq!( - Body::from_slice(b"test".as_ref()).size(), - BodySize::Sized(4) - ); - assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test"); - - assert_eq!((&b"test"[..]).size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| (&b"test"[..]).poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_vec() { - assert_eq!(Body::from(Vec::from("test")).size(), BodySize::Sized(4)); - assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); - - assert_eq!(Vec::from("test").size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| Vec::from("test").poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_bytes() { - let mut b = Bytes::from("test"); - assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b.clone()).get_ref(), b"test"); - - assert_eq!(b.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| b.poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_bytes_mut() { - let mut b = BytesMut::from("test"); - assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b.clone()).get_ref(), b"test"); - - assert_eq!(b.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| b.poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_string() { - let mut b = "test".to_owned(); - assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b.clone()).get_ref(), b"test"); - assert_eq!(Body::from(&b).size(), BodySize::Sized(4)); - assert_eq!(Body::from(&b).get_ref(), b"test"); - - assert_eq!(b.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| b.poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_unit() { - assert_eq!(().size(), BodySize::Empty); - assert!(poll_fn(|cx| ().poll_next(cx)).await.is_none()); - } - - #[actix_rt::test] - async fn test_box() { - let mut val = Box::new(()); - assert_eq!(val.size(), BodySize::Empty); - assert!(poll_fn(|cx| val.poll_next(cx)).await.is_none()); - } - - #[actix_rt::test] - async fn test_body_eq() { - assert!(Body::None == Body::None); - assert!(Body::None != Body::Empty); - assert!(Body::Empty == Body::Empty); - assert!(Body::Empty != Body::None); - assert!( - Body::Bytes(Bytes::from_static(b"1")) - == Body::Bytes(Bytes::from_static(b"1")) - ); - assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None); - } - - #[actix_rt::test] - async fn test_body_debug() { - assert!(format!("{:?}", Body::None).contains("Body::None")); - assert!(format!("{:?}", Body::Empty).contains("Body::Empty")); - assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1")); - } - - #[actix_rt::test] - async fn test_serde_json() { - use serde_json::json; - assert_eq!( - Body::from(serde_json::Value::String("test".into())).size(), - BodySize::Sized(6) - ); - assert_eq!( - Body::from(json!({"test-key":"test-value"})).size(), - BodySize::Sized(25) - ); - } - - mod body_stream { - use super::*; - - #[actix_rt::test] - async fn skips_empty_chunks() { - let mut body = BodyStream::new(stream::iter( - ["1", "", "2"] - .iter() - .map(|&v| Ok(Bytes::from(v)) as Result), - )); - assert_eq!( - poll_fn(|cx| body.poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("1")), - ); - assert_eq!( - poll_fn(|cx| body.poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("2")), - ); - } - } - - mod sized_stream { - use super::*; - - #[actix_rt::test] - async fn skips_empty_chunks() { - let mut body = SizedStream::new( - 2, - stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), - ); - assert_eq!( - poll_fn(|cx| body.poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("1")), - ); - assert_eq!( - poll_fn(|cx| body.poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("2")), - ); - } - } -} diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs deleted file mode 100644 index 271abd43f..000000000 --- a/actix-http/src/builder.rs +++ /dev/null @@ -1,251 +0,0 @@ -use std::marker::PhantomData; -use std::rc::Rc; -use std::{fmt, net}; - -use actix_codec::Framed; -use actix_service::{IntoServiceFactory, Service, ServiceFactory}; - -use crate::body::MessageBody; -use crate::config::{KeepAlive, ServiceConfig}; -use crate::error::Error; -use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler}; -use crate::h2::H2Service; -use crate::helpers::{Data, DataFactory}; -use crate::request::Request; -use crate::response::Response; -use crate::service::HttpService; - -/// A http service builder -/// -/// This type can be used to construct an instance of `http service` through a -/// builder-like pattern. -pub struct HttpServiceBuilder> { - keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, - secure: bool, - local_addr: Option, - expect: X, - upgrade: Option, - on_connect: Option Box>>, - _t: PhantomData<(T, S)>, -} - -impl HttpServiceBuilder> -where - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - ::Future: 'static, -{ - /// Create instance of `ServiceConfigBuilder` - pub fn new() -> Self { - HttpServiceBuilder { - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_disconnect: 0, - secure: false, - local_addr: None, - expect: ExpectHandler, - upgrade: None, - on_connect: None, - _t: PhantomData, - } - } -} - -impl HttpServiceBuilder -where - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - ::Future: 'static, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - ::Future: 'static, - U: ServiceFactory), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, - ::Future: 'static, -{ - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(mut self, val: W) -> Self { - self.keep_alive = val.into(); - self - } - - /// Set connection secure state - pub fn secure(mut self) -> Self { - self.secure = true; - self - } - - /// Set the local address that this service is bound to. - pub fn local_addr(mut self, addr: net::SocketAddr) -> Self { - self.local_addr = Some(addr); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } - - /// Set server connection disconnect timeout in milliseconds. - /// - /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete - /// within this time, the request get dropped. This timeout affects secure connections. - /// - /// To disable timeout set value to 0. - /// - /// By default disconnect timeout is set to 0. - pub fn client_disconnect(mut self, val: u64) -> Self { - self.client_disconnect = val; - self - } - - /// Provide service for `EXPECT: 100-Continue` support. - /// - /// Service get called with request that contains `EXPECT` header. - /// Service must return request in case of success, in that case - /// request will be forwarded to main service. - pub fn expect(self, expect: F) -> HttpServiceBuilder - where - F: IntoServiceFactory, - X1: ServiceFactory, - X1::Error: Into, - X1::InitError: fmt::Debug, - ::Future: 'static, - { - HttpServiceBuilder { - keep_alive: self.keep_alive, - client_timeout: self.client_timeout, - client_disconnect: self.client_disconnect, - secure: self.secure, - local_addr: self.local_addr, - expect: expect.into_factory(), - upgrade: self.upgrade, - on_connect: self.on_connect, - _t: PhantomData, - } - } - - /// Provide service for custom `Connection: UPGRADE` support. - /// - /// If service is provided then normal requests handling get halted - /// and this service get called with original request and framed object. - pub fn upgrade(self, upgrade: F) -> HttpServiceBuilder - where - F: IntoServiceFactory, - U1: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, - U1::Error: fmt::Display, - U1::InitError: fmt::Debug, - ::Future: 'static, - { - HttpServiceBuilder { - keep_alive: self.keep_alive, - client_timeout: self.client_timeout, - client_disconnect: self.client_disconnect, - secure: self.secure, - local_addr: self.local_addr, - expect: self.expect, - upgrade: Some(upgrade.into_factory()), - on_connect: self.on_connect, - _t: PhantomData, - } - } - - /// Set on-connect callback. - /// - /// It get called once per connection and result of the call - /// get stored to the request's extensions. - pub fn on_connect(mut self, f: F) -> Self - where - F: Fn(&T) -> I + 'static, - I: Clone + 'static, - { - self.on_connect = Some(Rc::new(move |io| Box::new(Data(f(io))))); - self - } - - /// Finish service configuration and create *http service* for HTTP/1 protocol. - pub fn h1(self, service: F) -> H1Service - where - B: MessageBody, - F: IntoServiceFactory, - S::Error: Into, - S::InitError: fmt::Debug, - S::Response: Into>, - { - let cfg = ServiceConfig::new( - self.keep_alive, - self.client_timeout, - self.client_disconnect, - self.secure, - self.local_addr, - ); - H1Service::with_config(cfg, service.into_factory()) - .expect(self.expect) - .upgrade(self.upgrade) - .on_connect(self.on_connect) - } - - /// Finish service configuration and create *http service* for HTTP/2 protocol. - pub fn h2(self, service: F) -> H2Service - where - B: MessageBody + 'static, - F: IntoServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - { - let cfg = ServiceConfig::new( - self.keep_alive, - self.client_timeout, - self.client_disconnect, - self.secure, - self.local_addr, - ); - H2Service::with_config(cfg, service.into_factory()).on_connect(self.on_connect) - } - - /// Finish service configuration and create `HttpService` instance. - pub fn finish(self, service: F) -> HttpService - where - B: MessageBody + 'static, - F: IntoServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - { - let cfg = ServiceConfig::new( - self.keep_alive, - self.client_timeout, - self.client_disconnect, - self.secure, - self.local_addr, - ); - HttpService::with_config(cfg, service.into_factory()) - .expect(self.expect) - .upgrade(self.upgrade) - .on_connect(self.on_connect) - } -} diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs deleted file mode 100644 index 0ca788b32..000000000 --- a/actix-http/src/client/connection.rs +++ /dev/null @@ -1,296 +0,0 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, io, mem, time}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use bytes::{Buf, Bytes}; -use futures_util::future::{err, Either, Future, FutureExt, LocalBoxFuture, Ready}; -use h2::client::SendRequest; -use pin_project::{pin_project, project}; - -use crate::body::MessageBody; -use crate::h1::ClientCodec; -use crate::message::{RequestHeadType, ResponseHead}; -use crate::payload::Payload; - -use super::error::SendRequestError; -use super::pool::{Acquired, Protocol}; -use super::{h1proto, h2proto}; - -pub(crate) enum ConnectionType { - H1(Io), - H2(SendRequest), -} - -pub trait Connection { - type Io: AsyncRead + AsyncWrite + Unpin; - type Future: Future>; - - fn protocol(&self) -> Protocol; - - /// Send request and body - fn send_request>( - self, - head: H, - body: B, - ) -> Self::Future; - - type TunnelFuture: Future< - Output = Result<(ResponseHead, Framed), SendRequestError>, - >; - - /// Send request, returns Response and Framed - fn open_tunnel>(self, head: H) -> Self::TunnelFuture; -} - -pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { - /// Close connection - fn close(&mut self); - - /// Release connection to the connection pool - fn release(&mut self); -} - -#[doc(hidden)] -/// HTTP client connection -pub struct IoConnection { - io: Option>, - created: time::Instant, - pool: Option>, -} - -impl fmt::Debug for IoConnection -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.io { - Some(ConnectionType::H1(ref io)) => write!(f, "H1Connection({:?})", io), - Some(ConnectionType::H2(_)) => write!(f, "H2Connection"), - None => write!(f, "Connection(Empty)"), - } - } -} - -impl IoConnection { - pub(crate) fn new( - io: ConnectionType, - created: time::Instant, - pool: Option>, - ) -> Self { - IoConnection { - pool, - created, - io: Some(io), - } - } - - pub(crate) fn into_inner(self) -> (ConnectionType, time::Instant) { - (self.io.unwrap(), self.created) - } -} - -impl Connection for IoConnection -where - T: AsyncRead + AsyncWrite + Unpin + 'static, -{ - type Io = T; - type Future = - LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>; - - fn protocol(&self) -> Protocol { - match self.io { - Some(ConnectionType::H1(_)) => Protocol::Http1, - Some(ConnectionType::H2(_)) => Protocol::Http2, - None => Protocol::Http1, - } - } - - fn send_request>( - mut self, - head: H, - body: B, - ) -> Self::Future { - match self.io.take().unwrap() { - ConnectionType::H1(io) => { - h1proto::send_request(io, head.into(), body, self.created, self.pool) - .boxed_local() - } - ConnectionType::H2(io) => { - h2proto::send_request(io, head.into(), body, self.created, self.pool) - .boxed_local() - } - } - } - - type TunnelFuture = Either< - LocalBoxFuture< - 'static, - Result<(ResponseHead, Framed), SendRequestError>, - >, - Ready), SendRequestError>>, - >; - - /// Send request, returns Response and Framed - fn open_tunnel>(mut self, head: H) -> Self::TunnelFuture { - match self.io.take().unwrap() { - ConnectionType::H1(io) => { - Either::Left(h1proto::open_tunnel(io, head.into()).boxed_local()) - } - ConnectionType::H2(io) => { - if let Some(mut pool) = self.pool.take() { - pool.release(IoConnection::new( - ConnectionType::H2(io), - self.created, - None, - )); - } - Either::Right(err(SendRequestError::TunnelNotSupported)) - } - } - } -} - -#[allow(dead_code)] -pub(crate) enum EitherConnection { - A(IoConnection), - B(IoConnection), -} - -impl Connection for EitherConnection -where - A: AsyncRead + AsyncWrite + Unpin + 'static, - B: AsyncRead + AsyncWrite + Unpin + 'static, -{ - type Io = EitherIo; - type Future = - LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>; - - fn protocol(&self) -> Protocol { - match self { - EitherConnection::A(con) => con.protocol(), - EitherConnection::B(con) => con.protocol(), - } - } - - fn send_request>( - self, - head: H, - body: RB, - ) -> Self::Future { - match self { - EitherConnection::A(con) => con.send_request(head, body), - EitherConnection::B(con) => con.send_request(head, body), - } - } - - type TunnelFuture = LocalBoxFuture< - 'static, - Result<(ResponseHead, Framed), SendRequestError>, - >; - - /// Send request, returns Response and Framed - fn open_tunnel>(self, head: H) -> Self::TunnelFuture { - match self { - EitherConnection::A(con) => con - .open_tunnel(head) - .map(|res| res.map(|(head, framed)| (head, framed.map_io(EitherIo::A)))) - .boxed_local(), - EitherConnection::B(con) => con - .open_tunnel(head) - .map(|res| res.map(|(head, framed)| (head, framed.map_io(EitherIo::B)))) - .boxed_local(), - } - } -} - -#[pin_project] -pub enum EitherIo { - A(#[pin] A), - B(#[pin] B), -} - -impl AsyncRead for EitherIo -where - A: AsyncRead, - B: AsyncRead, -{ - #[project] - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - #[project] - match self.project() { - EitherIo::A(val) => val.poll_read(cx, buf), - EitherIo::B(val) => val.poll_read(cx, buf), - } - } - - unsafe fn prepare_uninitialized_buffer( - &self, - buf: &mut [mem::MaybeUninit], - ) -> bool { - match self { - EitherIo::A(ref val) => val.prepare_uninitialized_buffer(buf), - EitherIo::B(ref val) => val.prepare_uninitialized_buffer(buf), - } - } -} - -impl AsyncWrite for EitherIo -where - A: AsyncWrite, - B: AsyncWrite, -{ - #[project] - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - #[project] - match self.project() { - EitherIo::A(val) => val.poll_write(cx, buf), - EitherIo::B(val) => val.poll_write(cx, buf), - } - } - - #[project] - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - #[project] - match self.project() { - EitherIo::A(val) => val.poll_flush(cx), - EitherIo::B(val) => val.poll_flush(cx), - } - } - - #[project] - fn poll_shutdown( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - #[project] - match self.project() { - EitherIo::A(val) => val.poll_shutdown(cx), - EitherIo::B(val) => val.poll_shutdown(cx), - } - } - - #[project] - fn poll_write_buf( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut U, - ) -> Poll> - where - Self: Sized, - { - #[project] - match self.project() { - EitherIo::A(val) => val.poll_write_buf(cx, buf), - EitherIo::B(val) => val.poll_write_buf(cx, buf), - } - } -} diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs deleted file mode 100644 index 055d4276d..000000000 --- a/actix-http/src/client/connector.rs +++ /dev/null @@ -1,529 +0,0 @@ -use std::fmt; -use std::marker::PhantomData; -use std::time::Duration; - -use actix_codec::{AsyncRead, AsyncWrite}; -use actix_connect::{ - default_connector, Connect as TcpConnect, Connection as TcpConnection, -}; -use actix_rt::net::TcpStream; -use actix_service::{apply_fn, Service}; -use actix_utils::timeout::{TimeoutError, TimeoutService}; -use http::Uri; - -use super::connection::Connection; -use super::error::ConnectError; -use super::pool::{ConnectionPool, Protocol}; -use super::Connect; - -#[cfg(feature = "openssl")] -use actix_connect::ssl::openssl::SslConnector as OpensslConnector; - -#[cfg(feature = "rustls")] -use actix_connect::ssl::rustls::ClientConfig; -#[cfg(feature = "rustls")] -use std::sync::Arc; - -#[cfg(any(feature = "openssl", feature = "rustls"))] -enum SslConnector { - #[cfg(feature = "openssl")] - Openssl(OpensslConnector), - #[cfg(feature = "rustls")] - Rustls(Arc), -} -#[cfg(not(any(feature = "openssl", feature = "rustls")))] -type SslConnector = (); - -/// Manages http client network connectivity -/// The `Connector` type uses a builder-like combinator pattern for service -/// construction that finishes by calling the `.finish()` method. -/// -/// ```rust,ignore -/// use std::time::Duration; -/// use actix_http::client::Connector; -/// -/// let connector = Connector::new() -/// .timeout(Duration::from_secs(5)) -/// .finish(); -/// ``` -pub struct Connector { - connector: T, - timeout: Duration, - conn_lifetime: Duration, - conn_keep_alive: Duration, - disconnect_timeout: Duration, - limit: usize, - #[allow(dead_code)] - ssl: SslConnector, - _t: PhantomData, -} - -trait Io: AsyncRead + AsyncWrite + Unpin {} -impl Io for T {} - -impl Connector<(), ()> { - #[allow(clippy::new_ret_no_self, clippy::let_unit_value)] - pub fn new() -> Connector< - impl Service< - Request = TcpConnect, - Response = TcpConnection, - Error = actix_connect::ConnectError, - > + Clone, - TcpStream, - > { - let ssl = { - #[cfg(feature = "openssl")] - { - use actix_connect::ssl::openssl::SslMethod; - - let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap(); - let _ = ssl - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| error!("Can not set alpn protocol: {:?}", e)); - SslConnector::Openssl(ssl.build()) - } - #[cfg(all(not(feature = "openssl"), feature = "rustls"))] - { - let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - let mut config = ClientConfig::new(); - config.set_protocols(&protos); - config - .root_store - .add_server_trust_anchors(&actix_tls::rustls::TLS_SERVER_ROOTS); - SslConnector::Rustls(Arc::new(config)) - } - #[cfg(not(any(feature = "openssl", feature = "rustls")))] - {} - }; - - Connector { - ssl, - connector: default_connector(), - timeout: Duration::from_secs(1), - conn_lifetime: Duration::from_secs(75), - conn_keep_alive: Duration::from_secs(15), - disconnect_timeout: Duration::from_millis(3000), - limit: 100, - _t: PhantomData, - } - } -} - -impl Connector { - /// Use custom connector. - pub fn connector(self, connector: T1) -> Connector - where - U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug, - T1: Service< - Request = TcpConnect, - Response = TcpConnection, - Error = actix_connect::ConnectError, - > + Clone, - { - Connector { - connector, - timeout: self.timeout, - conn_lifetime: self.conn_lifetime, - conn_keep_alive: self.conn_keep_alive, - disconnect_timeout: self.disconnect_timeout, - limit: self.limit, - ssl: self.ssl, - _t: PhantomData, - } - } -} - -impl Connector -where - U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, - T: Service< - Request = TcpConnect, - Response = TcpConnection, - Error = actix_connect::ConnectError, - > + Clone - + 'static, -{ - /// Connection timeout, i.e. max time to connect to remote host including dns name resolution. - /// Set to 1 second by default. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = timeout; - self - } - - #[cfg(feature = "openssl")] - /// Use custom `SslConnector` instance. - pub fn ssl(mut self, connector: OpensslConnector) -> Self { - self.ssl = SslConnector::Openssl(connector); - self - } - - #[cfg(feature = "rustls")] - pub fn rustls(mut self, connector: Arc) -> Self { - self.ssl = SslConnector::Rustls(connector); - self - } - - /// Set total number of simultaneous connections per type of scheme. - /// - /// If limit is 0, the connector has no limit. - /// The default limit size is 100. - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set keep-alive period for opened connection. - /// - /// Keep-alive period is the period between connection usage. If - /// the delay between repeated usages of the same connection - /// exceeds this period, the connection is closed. - /// Default keep-alive period is 15 seconds. - pub fn conn_keep_alive(mut self, dur: Duration) -> Self { - self.conn_keep_alive = dur; - self - } - - /// Set max lifetime period for connection. - /// - /// Connection lifetime is max lifetime of any opened connection - /// until it is closed regardless of keep-alive period. - /// Default lifetime period is 75 seconds. - pub fn conn_lifetime(mut self, dur: Duration) -> Self { - self.conn_lifetime = dur; - self - } - - /// Set server connection disconnect timeout in milliseconds. - /// - /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete - /// within this time, the socket get dropped. This timeout affects only secure connections. - /// - /// To disable timeout set value to 0. - /// - /// By default disconnect timeout is set to 3000 milliseconds. - pub fn disconnect_timeout(mut self, dur: Duration) -> Self { - self.disconnect_timeout = dur; - self - } - - /// Finish configuration process and create connector service. - /// The Connector builder always concludes by calling `finish()` last in - /// its combinator chain. - pub fn finish( - self, - ) -> impl Service - + Clone { - #[cfg(not(any(feature = "openssl", feature = "rustls")))] - { - let connector = TimeoutService::new( - self.timeout, - apply_fn(self.connector, |msg: Connect, srv| { - srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) - }) - .map_err(ConnectError::from) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), - ) - .map_err(|e| match e { - TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectError::Timeout, - }); - - connect_impl::InnerConnector { - tcp_pool: ConnectionPool::new( - connector, - self.conn_lifetime, - self.conn_keep_alive, - None, - self.limit, - ), - } - } - #[cfg(any(feature = "openssl", feature = "rustls"))] - { - const H2: &[u8] = b"h2"; - #[cfg(feature = "openssl")] - use actix_connect::ssl::openssl::OpensslConnector; - #[cfg(feature = "rustls")] - use actix_connect::ssl::rustls::{RustlsConnector, Session}; - use actix_service::{boxed::service, pipeline}; - - let ssl_service = TimeoutService::new( - self.timeout, - pipeline( - apply_fn(self.connector.clone(), |msg: Connect, srv| { - srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) - }) - .map_err(ConnectError::from), - ) - .and_then(match self.ssl { - #[cfg(feature = "openssl")] - SslConnector::Openssl(ssl) => service( - OpensslConnector::service(ssl) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (Box::new(sock) as Box, Protocol::Http2) - } else { - (Box::new(sock) as Box, Protocol::Http1) - } - }) - .map_err(ConnectError::from), - ), - #[cfg(feature = "rustls")] - SslConnector::Rustls(ssl) => service( - RustlsConnector::service(ssl) - .map_err(ConnectError::from) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .get_ref() - .1 - .get_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (Box::new(sock) as Box, Protocol::Http2) - } else { - (Box::new(sock) as Box, Protocol::Http1) - } - }), - ), - }), - ) - .map_err(|e| match e { - TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectError::Timeout, - }); - - let tcp_service = TimeoutService::new( - self.timeout, - apply_fn(self.connector, |msg: Connect, srv| { - srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) - }) - .map_err(ConnectError::from) - .map(|stream| (stream.into_parts().0, Protocol::Http1)), - ) - .map_err(|e| match e { - TimeoutError::Service(e) => e, - TimeoutError::Timeout => ConnectError::Timeout, - }); - - connect_impl::InnerConnector { - tcp_pool: ConnectionPool::new( - tcp_service, - self.conn_lifetime, - self.conn_keep_alive, - None, - self.limit, - ), - ssl_pool: ConnectionPool::new( - ssl_service, - self.conn_lifetime, - self.conn_keep_alive, - Some(self.disconnect_timeout), - self.limit, - ), - } - } - } -} - -#[cfg(not(any(feature = "openssl", feature = "rustls")))] -mod connect_impl { - use std::task::{Context, Poll}; - - use futures_util::future::{err, Either, Ready}; - - use super::*; - use crate::client::connection::IoConnection; - - pub(crate) struct InnerConnector - where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, - { - pub(crate) tcp_pool: ConnectionPool, - } - - impl Clone for InnerConnector - where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, - { - fn clone(&self) -> Self { - InnerConnector { - tcp_pool: self.tcp_pool.clone(), - } - } - } - - impl Service for InnerConnector - where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, - { - type Request = Connect; - type Response = IoConnection; - type Error = ConnectError; - type Future = Either< - as Service>::Future, - Ready, ConnectError>>, - >; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.tcp_pool.poll_ready(cx) - } - - fn call(&mut self, req: Connect) -> Self::Future { - match req.uri.scheme_str() { - Some("https") | Some("wss") => { - Either::Right(err(ConnectError::SslIsNotSupported)) - } - _ => Either::Left(self.tcp_pool.call(req)), - } - } - } -} - -#[cfg(any(feature = "openssl", feature = "rustls"))] -mod connect_impl { - use std::future::Future; - use std::marker::PhantomData; - use std::pin::Pin; - use std::task::{Context, Poll}; - - use futures_core::ready; - use futures_util::future::Either; - - use super::*; - use crate::client::connection::EitherConnection; - - pub(crate) struct InnerConnector - where - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service, - T2: Service, - { - pub(crate) tcp_pool: ConnectionPool, - pub(crate) ssl_pool: ConnectionPool, - } - - impl Clone for InnerConnector - where - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service - + 'static, - T2: Service - + 'static, - { - fn clone(&self) -> Self { - InnerConnector { - tcp_pool: self.tcp_pool.clone(), - ssl_pool: self.ssl_pool.clone(), - } - } - } - - impl Service for InnerConnector - where - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service - + 'static, - T2: Service - + 'static, - { - type Request = Connect; - type Response = EitherConnection; - type Error = ConnectError; - type Future = Either< - InnerConnectorResponseA, - InnerConnectorResponseB, - >; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.tcp_pool.poll_ready(cx) - } - - fn call(&mut self, req: Connect) -> Self::Future { - match req.uri.scheme_str() { - Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB { - fut: self.ssl_pool.call(req), - _t: PhantomData, - }), - _ => Either::Left(InnerConnectorResponseA { - fut: self.tcp_pool.call(req), - _t: PhantomData, - }), - } - } - } - - #[pin_project::pin_project] - pub(crate) struct InnerConnectorResponseA - where - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, - { - #[pin] - fut: as Service>::Future, - _t: PhantomData, - } - - impl Future for InnerConnectorResponseA - where - T: Service - + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - { - type Output = Result, ConnectError>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Poll::Ready( - ready!(Pin::new(&mut self.get_mut().fut).poll(cx)) - .map(EitherConnection::A), - ) - } - } - - #[pin_project::pin_project] - pub(crate) struct InnerConnectorResponseB - where - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, - { - #[pin] - fut: as Service>::Future, - _t: PhantomData, - } - - impl Future for InnerConnectorResponseB - where - T: Service - + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - { - type Output = Result, ConnectError>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Poll::Ready( - ready!(Pin::new(&mut self.get_mut().fut).poll(cx)) - .map(EitherConnection::B), - ) - } - } -} diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs deleted file mode 100644 index 42ea47ee8..000000000 --- a/actix-http/src/client/error.rs +++ /dev/null @@ -1,149 +0,0 @@ -use std::io; - -use actix_connect::resolver::ResolveError; -use derive_more::{Display, From}; - -#[cfg(feature = "openssl")] -use actix_connect::ssl::openssl::{HandshakeError, SslError}; - -use crate::error::{Error, ParseError, ResponseError}; -use crate::http::{Error as HttpError, StatusCode}; - -/// A set of errors that can occur while connecting to an HTTP host -#[derive(Debug, Display, From)] -pub enum ConnectError { - /// SSL feature is not enabled - #[display(fmt = "SSL is not supported")] - SslIsNotSupported, - - /// SSL error - #[cfg(feature = "openssl")] - #[display(fmt = "{}", _0)] - SslError(SslError), - - /// SSL Handshake error - #[cfg(feature = "openssl")] - #[display(fmt = "{}", _0)] - SslHandshakeError(String), - - /// Failed to resolve the hostname - #[display(fmt = "Failed resolving hostname: {}", _0)] - Resolver(ResolveError), - - /// No dns records - #[display(fmt = "No dns records found for the input")] - NoRecords, - - /// Http2 error - #[display(fmt = "{}", _0)] - H2(h2::Error), - - /// Connecting took too long - #[display(fmt = "Timeout out while establishing connection")] - Timeout, - - /// Connector has been disconnected - #[display(fmt = "Internal error: connector has been disconnected")] - Disconnected, - - /// Unresolved host name - #[display(fmt = "Connector received `Connect` method with unresolved host")] - Unresolverd, - - /// Connection io error - #[display(fmt = "{}", _0)] - Io(io::Error), -} - -impl From for ConnectError { - fn from(err: actix_connect::ConnectError) -> ConnectError { - match err { - actix_connect::ConnectError::Resolver(e) => ConnectError::Resolver(e), - actix_connect::ConnectError::NoRecords => ConnectError::NoRecords, - actix_connect::ConnectError::InvalidInput => panic!(), - actix_connect::ConnectError::Unresolverd => ConnectError::Unresolverd, - actix_connect::ConnectError::Io(e) => ConnectError::Io(e), - } - } -} - -#[cfg(feature = "openssl")] -impl From> for ConnectError { - fn from(err: HandshakeError) -> ConnectError { - ConnectError::SslHandshakeError(format!("{:?}", err)) - } -} - -#[derive(Debug, Display, From)] -pub enum InvalidUrl { - #[display(fmt = "Missing url scheme")] - MissingScheme, - #[display(fmt = "Unknown url scheme")] - UnknownScheme, - #[display(fmt = "Missing host name")] - MissingHost, - #[display(fmt = "Url parse error: {}", _0)] - HttpError(http::Error), -} - -/// A set of errors that can occur during request sending and response reading -#[derive(Debug, Display, From)] -pub enum SendRequestError { - /// Invalid URL - #[display(fmt = "Invalid URL: {}", _0)] - Url(InvalidUrl), - /// Failed to connect to host - #[display(fmt = "Failed to connect to host: {}", _0)] - Connect(ConnectError), - /// Error sending request - Send(io::Error), - /// Error parsing response - Response(ParseError), - /// Http error - #[display(fmt = "{}", _0)] - Http(HttpError), - /// Http2 error - #[display(fmt = "{}", _0)] - H2(h2::Error), - /// Response took too long - #[display(fmt = "Timeout out while waiting for response")] - Timeout, - /// Tunnels are not supported for http2 connection - #[display(fmt = "Tunnels are not supported for http2 connection")] - TunnelNotSupported, - /// Error sending request body - Body(Error), -} - -/// Convert `SendRequestError` to a server `Response` -impl ResponseError for SendRequestError { - fn status_code(&self) -> StatusCode { - match *self { - SendRequestError::Connect(ConnectError::Timeout) => { - StatusCode::GATEWAY_TIMEOUT - } - SendRequestError::Connect(_) => StatusCode::BAD_REQUEST, - _ => StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - -/// A set of errors that can occur during freezing a request -#[derive(Debug, Display, From)] -pub enum FreezeRequestError { - /// Invalid URL - #[display(fmt = "Invalid URL: {}", _0)] - Url(InvalidUrl), - /// Http error - #[display(fmt = "{}", _0)] - Http(HttpError), -} - -impl From for SendRequestError { - fn from(e: FreezeRequestError) -> Self { - match e { - FreezeRequestError::Url(e) => e.into(), - FreezeRequestError::Http(e) => e.into(), - } - } -} diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs deleted file mode 100644 index a0a20edf6..000000000 --- a/actix-http/src/client/h1proto.rs +++ /dev/null @@ -1,292 +0,0 @@ -use std::io::Write; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{io, mem, time}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use bytes::buf::BufMutExt; -use bytes::{Bytes, BytesMut}; -use futures_core::Stream; -use futures_util::future::poll_fn; -use futures_util::{SinkExt, StreamExt}; - -use crate::error::PayloadError; -use crate::h1; -use crate::header::HeaderMap; -use crate::http::header::{IntoHeaderValue, HOST}; -use crate::message::{RequestHeadType, ResponseHead}; -use crate::payload::{Payload, PayloadStream}; - -use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; -use super::error::{ConnectError, SendRequestError}; -use super::pool::Acquired; -use crate::body::{BodySize, MessageBody}; - -pub(crate) async fn send_request( - io: T, - mut head: RequestHeadType, - body: B, - created: time::Instant, - pool: Option>, -) -> Result<(ResponseHead, Payload), SendRequestError> -where - T: AsyncRead + AsyncWrite + Unpin + 'static, - B: MessageBody, -{ - // set request host header - if !head.as_ref().headers.contains_key(HOST) - && !head.extra_headers().iter().any(|h| h.contains_key(HOST)) - { - if let Some(host) = head.as_ref().uri.host() { - let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); - - let _ = match head.as_ref().uri.port_u16() { - None | Some(80) | Some(443) => write!(wrt, "{}", host), - Some(port) => write!(wrt, "{}:{}", host, port), - }; - - match wrt.get_mut().split().freeze().try_into() { - Ok(value) => match head { - RequestHeadType::Owned(ref mut head) => { - head.headers.insert(HOST, value) - } - RequestHeadType::Rc(_, ref mut extra_headers) => { - let headers = extra_headers.get_or_insert(HeaderMap::new()); - headers.insert(HOST, value) - } - }, - Err(e) => log::error!("Can not set HOST header {}", e), - } - } - } - - let io = H1Connection { - created, - pool, - io: Some(io), - }; - - // create Framed and send request - let mut framed = Framed::new(io, h1::ClientCodec::default()); - framed.send((head, body.size()).into()).await?; - - // send request body - match body.size() { - BodySize::None | BodySize::Empty | BodySize::Sized(0) => (), - _ => send_body(body, &mut framed).await?, - }; - - // read response and init read body - let res = framed.into_future().await; - let (head, framed) = if let (Some(result), framed) = res { - let item = result.map_err(SendRequestError::from)?; - (item, framed) - } else { - return Err(SendRequestError::from(ConnectError::Disconnected)); - }; - - match framed.get_codec().message_type() { - h1::MessageType::None => { - let force_close = !framed.get_codec().keepalive(); - release_connection(framed, force_close); - Ok((head, Payload::None)) - } - _ => { - let pl: PayloadStream = PlStream::new(framed).boxed_local(); - Ok((head, pl.into())) - } - } -} - -pub(crate) async fn open_tunnel( - io: T, - head: RequestHeadType, -) -> Result<(ResponseHead, Framed), SendRequestError> -where - T: AsyncRead + AsyncWrite + Unpin + 'static, -{ - // create Framed and send request - let mut framed = Framed::new(io, h1::ClientCodec::default()); - framed.send((head, BodySize::None).into()).await?; - - // read response - if let (Some(result), framed) = framed.into_future().await { - let head = result.map_err(SendRequestError::from)?; - Ok((head, framed)) - } else { - Err(SendRequestError::from(ConnectError::Disconnected)) - } -} - -/// send request body to the peer -pub(crate) async fn send_body( - mut body: B, - framed: &mut Framed, -) -> Result<(), SendRequestError> -where - I: ConnectionLifetime, - B: MessageBody, -{ - let mut eof = false; - while !eof { - while !eof && !framed.is_write_buf_full() { - match poll_fn(|cx| body.poll_next(cx)).await { - Some(result) => { - framed.write(h1::Message::Chunk(Some(result?)))?; - } - None => { - eof = true; - framed.write(h1::Message::Chunk(None))?; - } - } - } - - if !framed.is_write_buf_empty() { - poll_fn(|cx| match framed.flush(cx) { - Poll::Ready(Ok(_)) => Poll::Ready(Ok(())), - Poll::Ready(Err(err)) => Poll::Ready(Err(err)), - Poll::Pending => { - if !framed.is_write_buf_full() { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - } - }) - .await?; - } - } - - SinkExt::flush(framed).await?; - Ok(()) -} - -#[doc(hidden)] -/// HTTP client connection -pub struct H1Connection { - io: Option, - created: time::Instant, - pool: Option>, -} - -impl ConnectionLifetime for H1Connection -where - T: AsyncRead + AsyncWrite + Unpin + 'static, -{ - /// Close connection - fn close(&mut self) { - if let Some(mut pool) = self.pool.take() { - if let Some(io) = self.io.take() { - pool.close(IoConnection::new( - ConnectionType::H1(io), - self.created, - None, - )); - } - } - } - - /// Release this connection to the connection pool - fn release(&mut self) { - if let Some(mut pool) = self.pool.take() { - if let Some(io) = self.io.take() { - pool.release(IoConnection::new( - ConnectionType::H1(io), - self.created, - None, - )); - } - } - } -} - -impl AsyncRead for H1Connection { - unsafe fn prepare_uninitialized_buffer( - &self, - buf: &mut [mem::MaybeUninit], - ) -> bool { - self.io.as_ref().unwrap().prepare_uninitialized_buffer(buf) - } - - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - Pin::new(&mut self.io.as_mut().unwrap()).poll_read(cx, buf) - } -} - -impl AsyncWrite for H1Connection { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Pin::new(&mut self.io.as_mut().unwrap()).poll_write(cx, buf) - } - - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(self.io.as_mut().unwrap()).poll_flush(cx) - } - - fn poll_shutdown( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(self.io.as_mut().unwrap()).poll_shutdown(cx) - } -} - -pub(crate) struct PlStream { - framed: Option>, -} - -impl PlStream { - fn new(framed: Framed) -> Self { - PlStream { - framed: Some(framed.map_codec(|codec| codec.into_payload_codec())), - } - } -} - -impl Stream for PlStream { - type Item = Result; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - let this = self.get_mut(); - - match this.framed.as_mut().unwrap().next_item(cx)? { - Poll::Pending => Poll::Pending, - Poll::Ready(Some(chunk)) => { - if let Some(chunk) = chunk { - Poll::Ready(Some(Ok(chunk))) - } else { - let framed = this.framed.take().unwrap(); - let force_close = !framed.get_codec().keepalive(); - release_connection(framed, force_close); - Poll::Ready(None) - } - } - Poll::Ready(None) => Poll::Ready(None), - } - } -} - -fn release_connection(framed: Framed, force_close: bool) -where - T: ConnectionLifetime, -{ - let mut parts = framed.into_parts(); - if !force_close && parts.read_buf.is_empty() && parts.write_buf.is_empty() { - parts.io.release() - } else { - parts.io.close() - } -} diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs deleted file mode 100644 index eabf54e97..000000000 --- a/actix-http/src/client/h2proto.rs +++ /dev/null @@ -1,185 +0,0 @@ -use std::convert::TryFrom; -use std::time; - -use actix_codec::{AsyncRead, AsyncWrite}; -use bytes::Bytes; -use futures_util::future::poll_fn; -use h2::{client::SendRequest, SendStream}; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING}; -use http::{request::Request, Method, Version}; - -use crate::body::{BodySize, MessageBody}; -use crate::header::HeaderMap; -use crate::message::{RequestHeadType, ResponseHead}; -use crate::payload::Payload; - -use super::connection::{ConnectionType, IoConnection}; -use super::error::SendRequestError; -use super::pool::Acquired; - -pub(crate) async fn send_request( - mut io: SendRequest, - head: RequestHeadType, - body: B, - created: time::Instant, - pool: Option>, -) -> Result<(ResponseHead, Payload), SendRequestError> -where - T: AsyncRead + AsyncWrite + Unpin + 'static, - B: MessageBody, -{ - trace!("Sending client request: {:?} {:?}", head, body.size()); - let head_req = head.as_ref().method == Method::HEAD; - let length = body.size(); - let eof = match length { - BodySize::None | BodySize::Empty | BodySize::Sized(0) => true, - _ => false, - }; - - let mut req = Request::new(()); - *req.uri_mut() = head.as_ref().uri.clone(); - *req.method_mut() = head.as_ref().method.clone(); - *req.version_mut() = Version::HTTP_2; - - let mut skip_len = true; - // let mut has_date = false; - - // Content length - let _ = match length { - BodySize::None => None, - BodySize::Stream => { - skip_len = false; - None - } - BodySize::Empty => req - .headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), - BodySize::Sized(len) => req.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(format!("{}", len)).unwrap(), - ), - BodySize::Sized64(len) => req.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(format!("{}", len)).unwrap(), - ), - }; - - // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate. - let (head, extra_headers) = match head { - RequestHeadType::Owned(head) => (RequestHeadType::Owned(head), HeaderMap::new()), - RequestHeadType::Rc(head, extra_headers) => ( - RequestHeadType::Rc(head, None), - extra_headers.unwrap_or_else(HeaderMap::new), - ), - }; - - // merging headers from head and extra headers. - let headers = head - .as_ref() - .headers - .iter() - .filter(|(name, _)| !extra_headers.contains_key(*name)) - .chain(extra_headers.iter()); - - // copy headers - for (key, value) in headers { - match *key { - CONNECTION | TRANSFER_ENCODING => continue, // http2 specific - CONTENT_LENGTH if skip_len => continue, - // DATE => has_date = true, - _ => (), - } - req.headers_mut().append(key, value.clone()); - } - - let res = poll_fn(|cx| io.poll_ready(cx)).await; - if let Err(e) = res { - release(io, pool, created, e.is_io()); - return Err(SendRequestError::from(e)); - } - - let resp = match io.send_request(req, eof) { - Ok((fut, send)) => { - release(io, pool, created, false); - - if !eof { - send_body(body, send).await?; - } - fut.await.map_err(SendRequestError::from)? - } - Err(e) => { - release(io, pool, created, e.is_io()); - return Err(e.into()); - } - }; - - let (parts, body) = resp.into_parts(); - let payload = if head_req { Payload::None } else { body.into() }; - - let mut head = ResponseHead::new(parts.status); - head.version = parts.version; - head.headers = parts.headers.into(); - Ok((head, payload)) -} - -async fn send_body( - mut body: B, - mut send: SendStream, -) -> Result<(), SendRequestError> { - let mut buf = None; - loop { - if buf.is_none() { - match poll_fn(|cx| body.poll_next(cx)).await { - Some(Ok(b)) => { - send.reserve_capacity(b.len()); - buf = Some(b); - } - Some(Err(e)) => return Err(e.into()), - None => { - if let Err(e) = send.send_data(Bytes::new(), true) { - return Err(e.into()); - } - send.reserve_capacity(0); - return Ok(()); - } - } - } - - match poll_fn(|cx| send.poll_capacity(cx)).await { - None => return Ok(()), - Some(Ok(cap)) => { - let b = buf.as_mut().unwrap(); - let len = b.len(); - let bytes = b.split_to(std::cmp::min(cap, len)); - - if let Err(e) = send.send_data(bytes, false) { - return Err(e.into()); - } else { - if !b.is_empty() { - send.reserve_capacity(b.len()); - } else { - buf = None; - } - continue; - } - } - Some(Err(e)) => return Err(e.into()), - } - } -} - -// release SendRequest object -fn release( - io: SendRequest, - pool: Option>, - created: time::Instant, - close: bool, -) { - if let Some(mut pool) = pool { - if close { - pool.close(IoConnection::new(ConnectionType::H2(io), created, None)); - } else { - pool.release(IoConnection::new(ConnectionType::H2(io), created, None)); - } - } -} diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs deleted file mode 100644 index a45aebcd5..000000000 --- a/actix-http/src/client/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Http client api -use http::Uri; - -mod connection; -mod connector; -mod error; -mod h1proto; -mod h2proto; -mod pool; - -pub use self::connection::Connection; -pub use self::connector::Connector; -pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; -pub use self::pool::Protocol; - -#[derive(Clone)] -pub struct Connect { - pub uri: Uri, - pub addr: Option, -} diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs deleted file mode 100644 index 8c94423ac..000000000 --- a/actix-http/src/client/pool.rs +++ /dev/null @@ -1,633 +0,0 @@ -use std::cell::RefCell; -use std::collections::VecDeque; -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::time::{Duration, Instant}; - -use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::time::{delay_for, Delay}; -use actix_service::Service; -use actix_utils::{oneshot, task::LocalWaker}; -use bytes::Bytes; -use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture}; -use fxhash::FxHashMap; -use h2::client::{handshake, Connection, SendRequest}; -use http::uri::Authority; -use indexmap::IndexSet; -use slab::Slab; - -use super::connection::{ConnectionType, IoConnection}; -use super::error::ConnectError; -use super::Connect; - -#[derive(Clone, Copy, PartialEq)] -/// Protocol version -pub enum Protocol { - Http1, - Http2, -} - -#[derive(Hash, Eq, PartialEq, Clone, Debug)] -pub(crate) struct Key { - authority: Authority, -} - -impl From for Key { - fn from(authority: Authority) -> Key { - Key { authority } - } -} - -/// Connections pool -pub(crate) struct ConnectionPool(Rc>, Rc>>); - -impl ConnectionPool -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, -{ - pub(crate) fn new( - connector: T, - conn_lifetime: Duration, - conn_keep_alive: Duration, - disconnect_timeout: Option, - limit: usize, - ) -> Self { - ConnectionPool( - Rc::new(RefCell::new(connector)), - Rc::new(RefCell::new(Inner { - conn_lifetime, - conn_keep_alive, - disconnect_timeout, - limit, - acquired: 0, - waiters: Slab::new(), - waiters_queue: IndexSet::new(), - available: FxHashMap::default(), - waker: LocalWaker::new(), - })), - ) - } -} - -impl Clone for ConnectionPool -where - Io: 'static, -{ - fn clone(&self) -> Self { - ConnectionPool(self.0.clone(), self.1.clone()) - } -} - -impl Service for ConnectionPool -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, -{ - type Request = Connect; - type Response = IoConnection; - type Error = ConnectError; - type Future = LocalBoxFuture<'static, Result, ConnectError>>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.0.poll_ready(cx) - } - - fn call(&mut self, req: Connect) -> Self::Future { - // start support future - actix_rt::spawn(ConnectorPoolSupport { - connector: self.0.clone(), - inner: self.1.clone(), - }); - - let mut connector = self.0.clone(); - let inner = self.1.clone(); - - let fut = async move { - let key = if let Some(authority) = req.uri.authority() { - authority.clone().into() - } else { - return Err(ConnectError::Unresolverd); - }; - - // acquire connection - match poll_fn(|cx| Poll::Ready(inner.borrow_mut().acquire(&key, cx))).await { - Acquire::Acquired(io, created) => { - // use existing connection - return Ok(IoConnection::new( - io, - created, - Some(Acquired(key, Some(inner))), - )); - } - Acquire::Available => { - // open tcp connection - let (io, proto) = connector.call(req).await?; - - let guard = OpenGuard::new(key, inner); - - if proto == Protocol::Http1 { - Ok(IoConnection::new( - ConnectionType::H1(io), - Instant::now(), - Some(guard.consume()), - )) - } else { - let (snd, connection) = handshake(io).await?; - actix_rt::spawn(connection.map(|_| ())); - Ok(IoConnection::new( - ConnectionType::H2(snd), - Instant::now(), - Some(guard.consume()), - )) - } - } - _ => { - // connection is not available, wait - let (rx, token) = inner.borrow_mut().wait_for(req); - - let guard = WaiterGuard::new(key, token, inner); - let res = match rx.await { - Err(_) => Err(ConnectError::Disconnected), - Ok(res) => res, - }; - guard.consume(); - res - } - } - }; - - fut.boxed_local() - } -} - -struct WaiterGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - key: Key, - token: usize, - inner: Option>>>, -} - -impl WaiterGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn new(key: Key, token: usize, inner: Rc>>) -> Self { - Self { - key, - token, - inner: Some(inner), - } - } - - fn consume(mut self) { - let _ = self.inner.take(); - } -} - -impl Drop for WaiterGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn drop(&mut self) { - if let Some(i) = self.inner.take() { - let mut inner = i.as_ref().borrow_mut(); - inner.release_waiter(&self.key, self.token); - inner.check_availibility(); - } - } -} - -struct OpenGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - key: Key, - inner: Option>>>, -} - -impl OpenGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn new(key: Key, inner: Rc>>) -> Self { - Self { - key, - inner: Some(inner), - } - } - - fn consume(mut self) -> Acquired { - Acquired(self.key.clone(), self.inner.take()) - } -} - -impl Drop for OpenGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn drop(&mut self) { - if let Some(i) = self.inner.take() { - let mut inner = i.as_ref().borrow_mut(); - inner.release(); - inner.check_availibility(); - } - } -} - -enum Acquire { - Acquired(ConnectionType, Instant), - Available, - NotAvailable, -} - -struct AvailableConnection { - io: ConnectionType, - used: Instant, - created: Instant, -} - -pub(crate) struct Inner { - conn_lifetime: Duration, - conn_keep_alive: Duration, - disconnect_timeout: Option, - limit: usize, - acquired: usize, - available: FxHashMap>>, - waiters: Slab< - Option<( - Connect, - oneshot::Sender, ConnectError>>, - )>, - >, - waiters_queue: IndexSet<(Key, usize)>, - waker: LocalWaker, -} - -impl Inner { - fn reserve(&mut self) { - self.acquired += 1; - } - - fn release(&mut self) { - self.acquired -= 1; - } - - fn release_waiter(&mut self, key: &Key, token: usize) { - self.waiters.remove(token); - let _ = self.waiters_queue.shift_remove(&(key.clone(), token)); - } -} - -impl Inner -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - /// connection is not available, wait - fn wait_for( - &mut self, - connect: Connect, - ) -> ( - oneshot::Receiver, ConnectError>>, - usize, - ) { - let (tx, rx) = oneshot::channel(); - - let key: Key = connect.uri.authority().unwrap().clone().into(); - let entry = self.waiters.vacant_entry(); - let token = entry.key(); - entry.insert(Some((connect, tx))); - assert!(self.waiters_queue.insert((key, token))); - - (rx, token) - } - - fn acquire(&mut self, key: &Key, cx: &mut Context<'_>) -> Acquire { - // check limits - if self.limit > 0 && self.acquired >= self.limit { - return Acquire::NotAvailable; - } - - self.reserve(); - - // check if open connection is available - // cleanup stale connections at the same time - if let Some(ref mut connections) = self.available.get_mut(key) { - let now = Instant::now(); - while let Some(conn) = connections.pop_back() { - // check if it still usable - if (now - conn.used) > self.conn_keep_alive - || (now - conn.created) > self.conn_lifetime - { - if let Some(timeout) = self.disconnect_timeout { - if let ConnectionType::H1(io) = conn.io { - actix_rt::spawn(CloseConnection::new(io, timeout)) - } - } - } else { - let mut io = conn.io; - let mut buf = [0; 2]; - if let ConnectionType::H1(ref mut s) = io { - match Pin::new(s).poll_read(cx, &mut buf) { - Poll::Pending => (), - Poll::Ready(Ok(n)) if n > 0 => { - if let Some(timeout) = self.disconnect_timeout { - if let ConnectionType::H1(io) = io { - actix_rt::spawn(CloseConnection::new( - io, timeout, - )) - } - } - continue; - } - _ => continue, - } - } - return Acquire::Acquired(io, conn.created); - } - } - } - Acquire::Available - } - - fn release_conn(&mut self, key: &Key, io: ConnectionType, created: Instant) { - self.acquired -= 1; - self.available - .entry(key.clone()) - .or_insert_with(VecDeque::new) - .push_back(AvailableConnection { - io, - created, - used: Instant::now(), - }); - self.check_availibility(); - } - - fn release_close(&mut self, io: ConnectionType) { - self.acquired -= 1; - if let Some(timeout) = self.disconnect_timeout { - if let ConnectionType::H1(io) = io { - actix_rt::spawn(CloseConnection::new(io, timeout)) - } - } - self.check_availibility(); - } - - fn check_availibility(&self) { - if !self.waiters_queue.is_empty() && self.acquired < self.limit { - self.waker.wake(); - } - } -} - -struct CloseConnection { - io: T, - timeout: Delay, -} - -impl CloseConnection -where - T: AsyncWrite + Unpin, -{ - fn new(io: T, timeout: Duration) -> Self { - CloseConnection { - io, - timeout: delay_for(timeout), - } - } -} - -impl Future for CloseConnection -where - T: AsyncWrite + Unpin, -{ - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { - let this = self.get_mut(); - - match Pin::new(&mut this.timeout).poll(cx) { - Poll::Ready(_) => Poll::Ready(()), - Poll::Pending => match Pin::new(&mut this.io).poll_shutdown(cx) { - Poll::Ready(_) => Poll::Ready(()), - Poll::Pending => Poll::Pending, - }, - } - } -} - -struct ConnectorPoolSupport -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - connector: T, - inner: Rc>>, -} - -impl Future for ConnectorPoolSupport -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service, - T::Future: 'static, -{ - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = unsafe { self.get_unchecked_mut() }; - - let mut inner = this.inner.as_ref().borrow_mut(); - inner.waker.register(cx.waker()); - - // check waiters - loop { - let (key, token) = { - if let Some((key, token)) = inner.waiters_queue.get_index(0) { - (key.clone(), *token) - } else { - break; - } - }; - if inner.waiters.get(token).unwrap().is_none() { - continue; - } - - match inner.acquire(&key, cx) { - Acquire::NotAvailable => break, - Acquire::Acquired(io, created) => { - let tx = inner.waiters.get_mut(token).unwrap().take().unwrap().1; - if let Err(conn) = tx.send(Ok(IoConnection::new( - io, - created, - Some(Acquired(key.clone(), Some(this.inner.clone()))), - ))) { - let (io, created) = conn.unwrap().into_inner(); - inner.release_conn(&key, io, created); - } - } - Acquire::Available => { - let (connect, tx) = - inner.waiters.get_mut(token).unwrap().take().unwrap(); - OpenWaitingConnection::spawn( - key.clone(), - tx, - this.inner.clone(), - this.connector.call(connect), - ); - } - } - let _ = inner.waiters_queue.swap_remove_index(0); - } - - Poll::Pending - } -} - -#[pin_project::pin_project(PinnedDrop)] -struct OpenWaitingConnection -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - #[pin] - fut: F, - key: Key, - h2: Option< - LocalBoxFuture< - 'static, - Result<(SendRequest, Connection), h2::Error>, - >, - >, - rx: Option, ConnectError>>>, - inner: Option>>>, -} - -impl OpenWaitingConnection -where - F: Future> + 'static, - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn spawn( - key: Key, - rx: oneshot::Sender, ConnectError>>, - inner: Rc>>, - fut: F, - ) { - actix_rt::spawn(OpenWaitingConnection { - key, - fut, - h2: None, - rx: Some(rx), - inner: Some(inner), - }) - } -} - -#[pin_project::pinned_drop] -impl PinnedDrop for OpenWaitingConnection -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn drop(self: Pin<&mut Self>) { - if let Some(inner) = self.project().inner.take() { - let mut inner = inner.as_ref().borrow_mut(); - inner.release(); - inner.check_availibility(); - } - } -} - -impl Future for OpenWaitingConnection -where - F: Future>, - Io: AsyncRead + AsyncWrite + Unpin, -{ - type Output = (); - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - if let Some(ref mut h2) = this.h2 { - return match Pin::new(h2).poll(cx) { - Poll::Ready(Ok((snd, connection))) => { - actix_rt::spawn(connection.map(|_| ())); - let rx = this.rx.take().unwrap(); - let _ = rx.send(Ok(IoConnection::new( - ConnectionType::H2(snd), - Instant::now(), - Some(Acquired(this.key.clone(), this.inner.take())), - ))); - Poll::Ready(()) - } - Poll::Pending => Poll::Pending, - Poll::Ready(Err(err)) => { - let _ = this.inner.take(); - if let Some(rx) = this.rx.take() { - let _ = rx.send(Err(ConnectError::H2(err))); - } - Poll::Ready(()) - } - }; - } - - match this.fut.poll(cx) { - Poll::Ready(Err(err)) => { - let _ = this.inner.take(); - if let Some(rx) = this.rx.take() { - let _ = rx.send(Err(err)); - } - Poll::Ready(()) - } - Poll::Ready(Ok((io, proto))) => { - if proto == Protocol::Http1 { - let rx = this.rx.take().unwrap(); - let _ = rx.send(Ok(IoConnection::new( - ConnectionType::H1(io), - Instant::now(), - Some(Acquired(this.key.clone(), this.inner.take())), - ))); - Poll::Ready(()) - } else { - *this.h2 = Some(handshake(io).boxed_local()); - self.poll(cx) - } - } - Poll::Pending => Poll::Pending, - } - } -} - -pub(crate) struct Acquired(Key, Option>>>); - -impl Acquired -where - T: AsyncRead + AsyncWrite + Unpin + 'static, -{ - pub(crate) fn close(&mut self, conn: IoConnection) { - if let Some(inner) = self.1.take() { - let (io, _) = conn.into_inner(); - inner.as_ref().borrow_mut().release_close(io); - } - } - pub(crate) fn release(&mut self, conn: IoConnection) { - if let Some(inner) = self.1.take() { - let (io, created) = conn.into_inner(); - inner - .as_ref() - .borrow_mut() - .release_conn(&self.0, io, created); - } - } -} - -impl Drop for Acquired { - fn drop(&mut self) { - if let Some(inner) = self.1.take() { - inner.as_ref().borrow_mut().release(); - } - } -} diff --git a/actix-http/src/cloneable.rs b/actix-http/src/cloneable.rs deleted file mode 100644 index b64c299fc..000000000 --- a/actix-http/src/cloneable.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; -use std::task::{Context, Poll}; - -use actix_service::Service; - -#[doc(hidden)] -/// Service that allows to turn non-clone service to a service with `Clone` impl -/// -/// # Panics -/// CloneableService might panic with some creative use of thread local storage. -/// See https://github.com/actix/actix-web/issues/1295 for example -pub(crate) struct CloneableService(Rc>); - -impl CloneableService { - pub(crate) fn new(service: T) -> Self { - Self(Rc::new(RefCell::new(service))) - } -} - -impl Clone for CloneableService { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl Service for CloneableService { - type Request = T::Request; - type Response = T::Response; - type Error = T::Error; - type Future = T::Future; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.0.borrow_mut().poll_ready(cx) - } - - fn call(&mut self, req: T::Request) -> Self::Future { - self.0.borrow_mut().call(req) - } -} diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs deleted file mode 100644 index a38a80e76..000000000 --- a/actix-http/src/config.rs +++ /dev/null @@ -1,312 +0,0 @@ -use std::cell::Cell; -use std::fmt::Write; -use std::rc::Rc; -use std::time::Duration; -use std::{fmt, net}; - -use actix_rt::time::{delay_for, delay_until, Delay, Instant}; -use bytes::BytesMut; -use futures_util::{future, FutureExt}; -use time::OffsetDateTime; - -// "Sun, 06 Nov 1994 08:49:37 GMT".len() -const DATE_VALUE_LENGTH: usize = 29; - -#[derive(Debug, PartialEq, Clone, Copy)] -/// Server keep-alive setting -pub enum KeepAlive { - /// Keep alive in seconds - Timeout(usize), - /// Relay on OS to shutdown tcp connection - Os, - /// Disabled - Disabled, -} - -impl From for KeepAlive { - fn from(keepalive: usize) -> Self { - KeepAlive::Timeout(keepalive) - } -} - -impl From> for KeepAlive { - fn from(keepalive: Option) -> Self { - if let Some(keepalive) = keepalive { - KeepAlive::Timeout(keepalive) - } else { - KeepAlive::Disabled - } - } -} - -/// Http service configuration -pub struct ServiceConfig(Rc); - -struct Inner { - keep_alive: Option, - client_timeout: u64, - client_disconnect: u64, - ka_enabled: bool, - secure: bool, - local_addr: Option, - timer: DateService, -} - -impl Clone for ServiceConfig { - fn clone(&self) -> Self { - ServiceConfig(self.0.clone()) - } -} - -impl Default for ServiceConfig { - fn default() -> Self { - Self::new(KeepAlive::Timeout(5), 0, 0, false, None) - } -} - -impl ServiceConfig { - /// Create instance of `ServiceConfig` - pub fn new( - keep_alive: KeepAlive, - client_timeout: u64, - client_disconnect: u64, - secure: bool, - local_addr: Option, - ) -> ServiceConfig { - let (keep_alive, ka_enabled) = match keep_alive { - KeepAlive::Timeout(val) => (val as u64, true), - KeepAlive::Os => (0, true), - KeepAlive::Disabled => (0, false), - }; - let keep_alive = if ka_enabled && keep_alive > 0 { - Some(Duration::from_secs(keep_alive)) - } else { - None - }; - - ServiceConfig(Rc::new(Inner { - keep_alive, - ka_enabled, - client_timeout, - client_disconnect, - secure, - local_addr, - timer: DateService::new(), - })) - } - - #[inline] - /// Returns true if connection is secure(https) - pub fn secure(&self) -> bool { - self.0.secure - } - - #[inline] - /// Returns the local address that this server is bound to. - pub fn local_addr(&self) -> Option { - self.0.local_addr - } - - #[inline] - /// Keep alive duration if configured. - pub fn keep_alive(&self) -> Option { - self.0.keep_alive - } - - #[inline] - /// Return state of connection keep-alive funcitonality - pub fn keep_alive_enabled(&self) -> bool { - self.0.ka_enabled - } - - #[inline] - /// Client timeout for first request. - pub fn client_timer(&self) -> Option { - let delay_time = self.0.client_timeout; - if delay_time != 0 { - Some(delay_until( - self.0.timer.now() + Duration::from_millis(delay_time), - )) - } else { - None - } - } - - /// Client timeout for first request. - pub fn client_timer_expire(&self) -> Option { - let delay = self.0.client_timeout; - if delay != 0 { - Some(self.0.timer.now() + Duration::from_millis(delay)) - } else { - None - } - } - - /// Client disconnect timer - pub fn client_disconnect_timer(&self) -> Option { - let delay = self.0.client_disconnect; - if delay != 0 { - Some(self.0.timer.now() + Duration::from_millis(delay)) - } else { - None - } - } - - #[inline] - /// Return keep-alive timer delay is configured. - pub fn keep_alive_timer(&self) -> Option { - if let Some(ka) = self.0.keep_alive { - Some(delay_until(self.0.timer.now() + ka)) - } else { - None - } - } - - /// Keep-alive expire time - pub fn keep_alive_expire(&self) -> Option { - if let Some(ka) = self.0.keep_alive { - Some(self.0.timer.now() + ka) - } else { - None - } - } - - #[inline] - pub(crate) fn now(&self) -> Instant { - self.0.timer.now() - } - - #[doc(hidden)] - pub fn set_date(&self, dst: &mut BytesMut) { - let mut buf: [u8; 39] = [0; 39]; - buf[..6].copy_from_slice(b"date: "); - self.0 - .timer - .set_date(|date| buf[6..35].copy_from_slice(&date.bytes)); - buf[35..].copy_from_slice(b"\r\n\r\n"); - dst.extend_from_slice(&buf); - } - - pub(crate) fn set_date_header(&self, dst: &mut BytesMut) { - self.0 - .timer - .set_date(|date| dst.extend_from_slice(&date.bytes)); - } -} - -#[derive(Copy, Clone)] -struct Date { - bytes: [u8; DATE_VALUE_LENGTH], - pos: usize, -} - -impl Date { - fn new() -> Date { - let mut date = Date { - bytes: [0; DATE_VALUE_LENGTH], - pos: 0, - }; - date.update(); - date - } - fn update(&mut self) { - self.pos = 0; - write!(self, "{}", OffsetDateTime::now().format("%a, %d %b %Y %H:%M:%S GMT")).unwrap(); - } -} - -impl fmt::Write for Date { - fn write_str(&mut self, s: &str) -> fmt::Result { - let len = s.len(); - self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes()); - self.pos += len; - Ok(()) - } -} - -#[derive(Clone)] -struct DateService(Rc); - -struct DateServiceInner { - current: Cell>, -} - -impl DateServiceInner { - fn new() -> Self { - DateServiceInner { - current: Cell::new(None), - } - } - - fn reset(&self) { - self.current.take(); - } - - fn update(&self) { - let now = Instant::now(); - let date = Date::new(); - self.current.set(Some((date, now))); - } -} - -impl DateService { - fn new() -> Self { - DateService(Rc::new(DateServiceInner::new())) - } - - fn check_date(&self) { - if self.0.current.get().is_none() { - self.0.update(); - - // periodic date update - let s = self.clone(); - actix_rt::spawn(delay_for(Duration::from_millis(500)).then(move |_| { - s.0.reset(); - future::ready(()) - })); - } - } - - fn now(&self) -> Instant { - self.check_date(); - self.0.current.get().unwrap().1 - } - - fn set_date(&self, mut f: F) { - self.check_date(); - f(&self.0.current.get().unwrap().0); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - - // Test modifying the date from within the closure - // passed to `set_date` - #[test] - fn test_evil_date() { - let service = DateService::new(); - // Make sure that `check_date` doesn't try to spawn a task - service.0.update(); - service.set_date(|_| { - service.0.reset() - }); - } - - #[test] - fn test_date_len() { - assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len()); - } - - #[actix_rt::test] - async fn test_date() { - let settings = ServiceConfig::new(KeepAlive::Os, 0, 0, false, None); - let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1); - let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2); - assert_eq!(buf1, buf2); - } -} diff --git a/actix-http/src/cookie/builder.rs b/actix-http/src/cookie/builder.rs deleted file mode 100644 index c3820abf0..000000000 --- a/actix-http/src/cookie/builder.rs +++ /dev/null @@ -1,251 +0,0 @@ -use std::borrow::Cow; - -use time::{Duration, OffsetDateTime}; - -use super::{Cookie, SameSite}; - -/// Structure that follows the builder pattern for building `Cookie` structs. -/// -/// To construct a cookie: -/// -/// 1. Call [`Cookie::build`](struct.Cookie.html#method.build) to start building. -/// 2. Use any of the builder methods to set fields in the cookie. -/// 3. Call [finish](#method.finish) to retrieve the built cookie. -/// -/// # Example -/// -/// ```rust -/// use actix_http::cookie::Cookie; -/// -/// let cookie: Cookie = Cookie::build("name", "value") -/// .domain("www.rust-lang.org") -/// .path("/") -/// .secure(true) -/// .http_only(true) -/// .max_age(84600) -/// .finish(); -/// ``` -#[derive(Debug, Clone)] -pub struct CookieBuilder { - /// The cookie being built. - cookie: Cookie<'static>, -} - -impl CookieBuilder { - /// Creates a new `CookieBuilder` instance from the given name and value. - /// - /// This method is typically called indirectly via - /// [Cookie::build](struct.Cookie.html#method.build). - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::build("foo", "bar").finish(); - /// assert_eq!(c.name_value(), ("foo", "bar")); - /// ``` - pub fn new(name: N, value: V) -> CookieBuilder - where - N: Into>, - V: Into>, - { - CookieBuilder { - cookie: Cookie::new(name, value), - } - } - - /// Sets the `expires` field in the cookie being built. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::build("foo", "bar") - /// .expires(time::OffsetDateTime::now()) - /// .finish(); - /// - /// assert!(c.expires().is_some()); - /// ``` - #[inline] - pub fn expires(mut self, when: OffsetDateTime) -> CookieBuilder { - self.cookie.set_expires(when); - self - } - - /// Sets the `max_age` field in seconds in the cookie being built. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::build("foo", "bar") - /// .max_age(1800) - /// .finish(); - /// - /// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60))); - /// ``` - #[inline] - pub fn max_age(self, seconds: i64) -> CookieBuilder { - self.max_age_time(Duration::seconds(seconds)) - } - - /// Sets the `max_age` field in the cookie being built. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::build("foo", "bar") - /// .max_age_time(time::Duration::minutes(30)) - /// .finish(); - /// - /// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60))); - /// ``` - #[inline] - pub fn max_age_time(mut self, value: Duration) -> CookieBuilder { - // Truncate any nanoseconds from the Duration, as they aren't represented within `Max-Age` - // and would cause two otherwise identical `Cookie` instances to not be equivalent to one another. - self.cookie.set_max_age(Duration::seconds(value.whole_seconds())); - self - } - - /// Sets the `domain` field in the cookie being built. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::build("foo", "bar") - /// .domain("www.rust-lang.org") - /// .finish(); - /// - /// assert_eq!(c.domain(), Some("www.rust-lang.org")); - /// ``` - pub fn domain>>(mut self, value: D) -> CookieBuilder { - self.cookie.set_domain(value); - self - } - - /// Sets the `path` field in the cookie being built. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::build("foo", "bar") - /// .path("/") - /// .finish(); - /// - /// assert_eq!(c.path(), Some("/")); - /// ``` - pub fn path>>(mut self, path: P) -> CookieBuilder { - self.cookie.set_path(path); - self - } - - /// Sets the `secure` field in the cookie being built. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::build("foo", "bar") - /// .secure(true) - /// .finish(); - /// - /// assert_eq!(c.secure(), Some(true)); - /// ``` - #[inline] - pub fn secure(mut self, value: bool) -> CookieBuilder { - self.cookie.set_secure(value); - self - } - - /// Sets the `http_only` field in the cookie being built. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::build("foo", "bar") - /// .http_only(true) - /// .finish(); - /// - /// assert_eq!(c.http_only(), Some(true)); - /// ``` - #[inline] - pub fn http_only(mut self, value: bool) -> CookieBuilder { - self.cookie.set_http_only(value); - self - } - - /// Sets the `same_site` field in the cookie being built. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{Cookie, SameSite}; - /// - /// let c = Cookie::build("foo", "bar") - /// .same_site(SameSite::Strict) - /// .finish(); - /// - /// assert_eq!(c.same_site(), Some(SameSite::Strict)); - /// ``` - #[inline] - pub fn same_site(mut self, value: SameSite) -> CookieBuilder { - self.cookie.set_same_site(value); - self - } - - /// Makes the cookie being built 'permanent' by extending its expiration and - /// max age 20 years into the future. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// use time::Duration; - /// - /// let c = Cookie::build("foo", "bar") - /// .permanent() - /// .finish(); - /// - /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20))); - /// # assert!(c.expires().is_some()); - /// ``` - #[inline] - pub fn permanent(mut self) -> CookieBuilder { - self.cookie.make_permanent(); - self - } - - /// Finishes building and returns the built `Cookie`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::build("foo", "bar") - /// .domain("crates.io") - /// .path("/") - /// .finish(); - /// - /// assert_eq!(c.name_value(), ("foo", "bar")); - /// assert_eq!(c.domain(), Some("crates.io")); - /// assert_eq!(c.path(), Some("/")); - /// ``` - #[inline] - pub fn finish(self) -> Cookie<'static> { - self.cookie - } -} diff --git a/actix-http/src/cookie/delta.rs b/actix-http/src/cookie/delta.rs deleted file mode 100644 index a001a5bb8..000000000 --- a/actix-http/src/cookie/delta.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::borrow::Borrow; -use std::hash::{Hash, Hasher}; -use std::ops::{Deref, DerefMut}; - -use super::Cookie; - -/// A `DeltaCookie` is a helper structure used in a cookie jar. It wraps a -/// `Cookie` so that it can be hashed and compared purely by name. It further -/// records whether the wrapped cookie is a "removal" cookie, that is, a cookie -/// that when sent to the client removes the named cookie on the client's -/// machine. -#[derive(Clone, Debug)] -pub struct DeltaCookie { - pub cookie: Cookie<'static>, - pub removed: bool, -} - -impl DeltaCookie { - /// Create a new `DeltaCookie` that is being added to a jar. - #[inline] - pub fn added(cookie: Cookie<'static>) -> DeltaCookie { - DeltaCookie { - cookie, - removed: false, - } - } - - /// Create a new `DeltaCookie` that is being removed from a jar. The - /// `cookie` should be a "removal" cookie. - #[inline] - pub fn removed(cookie: Cookie<'static>) -> DeltaCookie { - DeltaCookie { - cookie, - removed: true, - } - } -} - -impl Deref for DeltaCookie { - type Target = Cookie<'static>; - - fn deref(&self) -> &Cookie<'static> { - &self.cookie - } -} - -impl DerefMut for DeltaCookie { - fn deref_mut(&mut self) -> &mut Cookie<'static> { - &mut self.cookie - } -} - -impl PartialEq for DeltaCookie { - fn eq(&self, other: &DeltaCookie) -> bool { - self.name() == other.name() - } -} - -impl Eq for DeltaCookie {} - -impl Hash for DeltaCookie { - fn hash(&self, state: &mut H) { - self.name().hash(state); - } -} - -impl Borrow for DeltaCookie { - fn borrow(&self) -> &str { - self.name() - } -} diff --git a/actix-http/src/cookie/draft.rs b/actix-http/src/cookie/draft.rs deleted file mode 100644 index a6525a605..000000000 --- a/actix-http/src/cookie/draft.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! This module contains types that represent cookie properties that are not yet -//! standardized. That is, _draft_ features. - -use std::fmt; - -/// The `SameSite` cookie attribute. -/// -/// A cookie with a `SameSite` attribute is imposed restrictions on when it is -/// sent to the origin server in a cross-site request. If the `SameSite` -/// attribute is "Strict", then the cookie is never sent in cross-site requests. -/// If the `SameSite` attribute is "Lax", the cookie is only sent in cross-site -/// requests with "safe" HTTP methods, i.e, `GET`, `HEAD`, `OPTIONS`, `TRACE`. -/// If the `SameSite` attribute is not present then the cookie will be sent as -/// normal. In some browsers, this will implicitly handle the cookie as if "Lax" -/// and in others, "None". It's best to explicitly set the `SameSite` attribute -/// to avoid inconsistent behavior. -/// -/// **Note:** Depending on browser, the `Secure` attribute may be required for -/// `SameSite` "None" cookies to be accepted. -/// -/// **Note:** This cookie attribute is an HTTP draft! Its meaning and definition -/// are subject to change. -/// -/// More info about these draft changes can be found in the draft spec: -/// - https://tools.ietf.org/html/draft-west-cookie-incrementalism-00 -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum SameSite { - /// The "Strict" `SameSite` attribute. - Strict, - /// The "Lax" `SameSite` attribute. - Lax, - /// The "None" `SameSite` attribute. - None, -} - -impl SameSite { - /// Returns `true` if `self` is `SameSite::Strict` and `false` otherwise. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::SameSite; - /// - /// let strict = SameSite::Strict; - /// assert!(strict.is_strict()); - /// assert!(!strict.is_lax()); - /// assert!(!strict.is_none()); - /// ``` - #[inline] - pub fn is_strict(self) -> bool { - match self { - SameSite::Strict => true, - SameSite::Lax | SameSite::None => false, - } - } - - /// Returns `true` if `self` is `SameSite::Lax` and `false` otherwise. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::SameSite; - /// - /// let lax = SameSite::Lax; - /// assert!(lax.is_lax()); - /// assert!(!lax.is_strict()); - /// assert!(!lax.is_none()); - /// ``` - #[inline] - pub fn is_lax(self) -> bool { - match self { - SameSite::Lax => true, - SameSite::Strict | SameSite::None => false, - } - } - - /// Returns `true` if `self` is `SameSite::None` and `false` otherwise. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::SameSite; - /// - /// let none = SameSite::None; - /// assert!(none.is_none()); - /// assert!(!none.is_lax()); - /// assert!(!none.is_strict()); - /// ``` - #[inline] - pub fn is_none(self) -> bool { - match self { - SameSite::None => true, - SameSite::Lax | SameSite::Strict => false, - } - } -} - -impl fmt::Display for SameSite { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - SameSite::Strict => write!(f, "Strict"), - SameSite::Lax => write!(f, "Lax"), - SameSite::None => write!(f, "None"), - } - } -} diff --git a/actix-http/src/cookie/jar.rs b/actix-http/src/cookie/jar.rs deleted file mode 100644 index 64922897b..000000000 --- a/actix-http/src/cookie/jar.rs +++ /dev/null @@ -1,651 +0,0 @@ -use std::collections::HashSet; -use std::mem::replace; - -use time::{Duration, OffsetDateTime}; - -use super::delta::DeltaCookie; -use super::Cookie; - -#[cfg(feature = "secure-cookies")] -use super::secure::{Key, PrivateJar, SignedJar}; - -/// A collection of cookies that tracks its modifications. -/// -/// A `CookieJar` provides storage for any number of cookies. Any changes made -/// to the jar are tracked; the changes can be retrieved via the -/// [delta](#method.delta) method which returns an interator over the changes. -/// -/// # Usage -/// -/// A jar's life begins via [new](#method.new) and calls to -/// [`add_original`](#method.add_original): -/// -/// ```rust -/// use actix_http::cookie::{Cookie, CookieJar}; -/// -/// let mut jar = CookieJar::new(); -/// jar.add_original(Cookie::new("name", "value")); -/// jar.add_original(Cookie::new("second", "another")); -/// ``` -/// -/// Cookies can be added via [add](#method.add) and removed via -/// [remove](#method.remove). Finally, cookies can be looked up via -/// [get](#method.get): -/// -/// ```rust -/// # use actix_http::cookie::{Cookie, CookieJar}; -/// let mut jar = CookieJar::new(); -/// jar.add(Cookie::new("a", "one")); -/// jar.add(Cookie::new("b", "two")); -/// -/// assert_eq!(jar.get("a").map(|c| c.value()), Some("one")); -/// assert_eq!(jar.get("b").map(|c| c.value()), Some("two")); -/// -/// jar.remove(Cookie::named("b")); -/// assert!(jar.get("b").is_none()); -/// ``` -/// -/// # Deltas -/// -/// A jar keeps track of any modifications made to it over time. The -/// modifications are recorded as cookies. The modifications can be retrieved -/// via [delta](#method.delta). Any new `Cookie` added to a jar via `add` -/// results in the same `Cookie` appearing in the `delta`; cookies added via -/// `add_original` do not count towards the delta. Any _original_ cookie that is -/// removed from a jar results in a "removal" cookie appearing in the delta. A -/// "removal" cookie is a cookie that a server sends so that the cookie is -/// removed from the client's machine. -/// -/// Deltas are typically used to create `Set-Cookie` headers corresponding to -/// the changes made to a cookie jar over a period of time. -/// -/// ```rust -/// # use actix_http::cookie::{Cookie, CookieJar}; -/// let mut jar = CookieJar::new(); -/// -/// // original cookies don't affect the delta -/// jar.add_original(Cookie::new("original", "value")); -/// assert_eq!(jar.delta().count(), 0); -/// -/// // new cookies result in an equivalent `Cookie` in the delta -/// jar.add(Cookie::new("a", "one")); -/// jar.add(Cookie::new("b", "two")); -/// assert_eq!(jar.delta().count(), 2); -/// -/// // removing an original cookie adds a "removal" cookie to the delta -/// jar.remove(Cookie::named("original")); -/// assert_eq!(jar.delta().count(), 3); -/// -/// // removing a new cookie that was added removes that `Cookie` from the delta -/// jar.remove(Cookie::named("a")); -/// assert_eq!(jar.delta().count(), 2); -/// ``` -#[derive(Default, Debug, Clone)] -pub struct CookieJar { - original_cookies: HashSet, - delta_cookies: HashSet, -} - -impl CookieJar { - /// Creates an empty cookie jar. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::CookieJar; - /// - /// let jar = CookieJar::new(); - /// assert_eq!(jar.iter().count(), 0); - /// ``` - pub fn new() -> CookieJar { - CookieJar::default() - } - - /// Returns a reference to the `Cookie` inside this jar with the name - /// `name`. If no such cookie exists, returns `None`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie}; - /// - /// let mut jar = CookieJar::new(); - /// assert!(jar.get("name").is_none()); - /// - /// jar.add(Cookie::new("name", "value")); - /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value")); - /// ``` - pub fn get(&self, name: &str) -> Option<&Cookie<'static>> { - self.delta_cookies - .get(name) - .or_else(|| self.original_cookies.get(name)) - .and_then(|c| if !c.removed { Some(&c.cookie) } else { None }) - } - - /// Adds an "original" `cookie` to this jar. If an original cookie with the - /// same name already exists, it is replaced with `cookie`. Cookies added - /// with `add` take precedence and are not replaced by this method. - /// - /// Adding an original cookie does not affect the [delta](#method.delta) - /// computation. This method is intended to be used to seed the cookie jar - /// with cookies received from a client's HTTP message. - /// - /// For accurate `delta` computations, this method should not be called - /// after calling `remove`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie}; - /// - /// let mut jar = CookieJar::new(); - /// jar.add_original(Cookie::new("name", "value")); - /// jar.add_original(Cookie::new("second", "two")); - /// - /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value")); - /// assert_eq!(jar.get("second").map(|c| c.value()), Some("two")); - /// assert_eq!(jar.iter().count(), 2); - /// assert_eq!(jar.delta().count(), 0); - /// ``` - pub fn add_original(&mut self, cookie: Cookie<'static>) { - self.original_cookies.replace(DeltaCookie::added(cookie)); - } - - /// Adds `cookie` to this jar. If a cookie with the same name already - /// exists, it is replaced with `cookie`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie}; - /// - /// let mut jar = CookieJar::new(); - /// jar.add(Cookie::new("name", "value")); - /// jar.add(Cookie::new("second", "two")); - /// - /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value")); - /// assert_eq!(jar.get("second").map(|c| c.value()), Some("two")); - /// assert_eq!(jar.iter().count(), 2); - /// assert_eq!(jar.delta().count(), 2); - /// ``` - pub fn add(&mut self, cookie: Cookie<'static>) { - self.delta_cookies.replace(DeltaCookie::added(cookie)); - } - - /// Removes `cookie` from this jar. If an _original_ cookie with the same - /// name as `cookie` is present in the jar, a _removal_ cookie will be - /// present in the `delta` computation. To properly generate the removal - /// cookie, `cookie` must contain the same `path` and `domain` as the cookie - /// that was initially set. - /// - /// A "removal" cookie is a cookie that has the same name as the original - /// cookie but has an empty value, a max-age of 0, and an expiration date - /// far in the past. - /// - /// # Example - /// - /// Removing an _original_ cookie results in a _removal_ cookie: - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie}; - /// use time::Duration; - /// - /// let mut jar = CookieJar::new(); - /// - /// // Assume this cookie originally had a path of "/" and domain of "a.b". - /// jar.add_original(Cookie::new("name", "value")); - /// - /// // If the path and domain were set, they must be provided to `remove`. - /// jar.remove(Cookie::build("name", "").path("/").domain("a.b").finish()); - /// - /// // The delta will contain the removal cookie. - /// let delta: Vec<_> = jar.delta().collect(); - /// assert_eq!(delta.len(), 1); - /// assert_eq!(delta[0].name(), "name"); - /// assert_eq!(delta[0].max_age(), Some(Duration::zero())); - /// ``` - /// - /// Removing a new cookie does not result in a _removal_ cookie: - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie}; - /// - /// let mut jar = CookieJar::new(); - /// jar.add(Cookie::new("name", "value")); - /// assert_eq!(jar.delta().count(), 1); - /// - /// jar.remove(Cookie::named("name")); - /// assert_eq!(jar.delta().count(), 0); - /// ``` - pub fn remove(&mut self, mut cookie: Cookie<'static>) { - if self.original_cookies.contains(cookie.name()) { - cookie.set_value(""); - cookie.set_max_age(Duration::zero()); - cookie.set_expires(OffsetDateTime::now() - Duration::days(365)); - self.delta_cookies.replace(DeltaCookie::removed(cookie)); - } else { - self.delta_cookies.remove(cookie.name()); - } - } - - /// Removes `cookie` from this jar completely. This method differs from - /// `remove` in that no delta cookie is created under any condition. Neither - /// the `delta` nor `iter` methods will return a cookie that is removed - /// using this method. - /// - /// # Example - /// - /// Removing an _original_ cookie; no _removal_ cookie is generated: - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie}; - /// use time::Duration; - /// - /// let mut jar = CookieJar::new(); - /// - /// // Add an original cookie and a new cookie. - /// jar.add_original(Cookie::new("name", "value")); - /// jar.add(Cookie::new("key", "value")); - /// assert_eq!(jar.delta().count(), 1); - /// assert_eq!(jar.iter().count(), 2); - /// - /// // Now force remove the original cookie. - /// jar.force_remove(Cookie::new("name", "value")); - /// assert_eq!(jar.delta().count(), 1); - /// assert_eq!(jar.iter().count(), 1); - /// - /// // Now force remove the new cookie. - /// jar.force_remove(Cookie::new("key", "value")); - /// assert_eq!(jar.delta().count(), 0); - /// assert_eq!(jar.iter().count(), 0); - /// ``` - pub fn force_remove<'a>(&mut self, cookie: Cookie<'a>) { - self.original_cookies.remove(cookie.name()); - self.delta_cookies.remove(cookie.name()); - } - - /// Removes all cookies from this cookie jar. - #[deprecated( - since = "0.7.0", - note = "calling this method may not remove \ - all cookies since the path and domain are not specified; use \ - `remove` instead" - )] - pub fn clear(&mut self) { - self.delta_cookies.clear(); - for delta in replace(&mut self.original_cookies, HashSet::new()) { - self.remove(delta.cookie); - } - } - - /// Returns an iterator over cookies that represent the changes to this jar - /// over time. These cookies can be rendered directly as `Set-Cookie` header - /// values to affect the changes made to this jar on the client. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie}; - /// - /// let mut jar = CookieJar::new(); - /// jar.add_original(Cookie::new("name", "value")); - /// jar.add_original(Cookie::new("second", "two")); - /// - /// // Add new cookies. - /// jar.add(Cookie::new("new", "third")); - /// jar.add(Cookie::new("another", "fourth")); - /// jar.add(Cookie::new("yac", "fifth")); - /// - /// // Remove some cookies. - /// jar.remove(Cookie::named("name")); - /// jar.remove(Cookie::named("another")); - /// - /// // Delta contains two new cookies ("new", "yac") and a removal ("name"). - /// assert_eq!(jar.delta().count(), 3); - /// ``` - pub fn delta(&self) -> Delta<'_> { - Delta { - iter: self.delta_cookies.iter(), - } - } - - /// Returns an iterator over all of the cookies present in this jar. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie}; - /// - /// let mut jar = CookieJar::new(); - /// - /// jar.add_original(Cookie::new("name", "value")); - /// jar.add_original(Cookie::new("second", "two")); - /// - /// jar.add(Cookie::new("new", "third")); - /// jar.add(Cookie::new("another", "fourth")); - /// jar.add(Cookie::new("yac", "fifth")); - /// - /// jar.remove(Cookie::named("name")); - /// jar.remove(Cookie::named("another")); - /// - /// // There are three cookies in the jar: "second", "new", and "yac". - /// # assert_eq!(jar.iter().count(), 3); - /// for cookie in jar.iter() { - /// match cookie.name() { - /// "second" => assert_eq!(cookie.value(), "two"), - /// "new" => assert_eq!(cookie.value(), "third"), - /// "yac" => assert_eq!(cookie.value(), "fifth"), - /// _ => unreachable!("there are only three cookies in the jar") - /// } - /// } - /// ``` - pub fn iter(&self) -> Iter<'_> { - Iter { - delta_cookies: self - .delta_cookies - .iter() - .chain(self.original_cookies.difference(&self.delta_cookies)), - } - } - - /// Returns a `PrivateJar` with `self` as its parent jar using the key `key` - /// to sign/encrypt and verify/decrypt cookies added/retrieved from the - /// child jar. - /// - /// Any modifications to the child jar will be reflected on the parent jar, - /// and any retrievals from the child jar will be made from the parent jar. - /// - /// This method is only available when the `secure` feature is enabled. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{Cookie, CookieJar, Key}; - /// - /// // Generate a secure key. - /// let key = Key::generate(); - /// - /// // Add a private (signed + encrypted) cookie. - /// let mut jar = CookieJar::new(); - /// jar.private(&key).add(Cookie::new("private", "text")); - /// - /// // The cookie's contents are encrypted. - /// assert_ne!(jar.get("private").unwrap().value(), "text"); - /// - /// // They can be decrypted and verified through the child jar. - /// assert_eq!(jar.private(&key).get("private").unwrap().value(), "text"); - /// - /// // A tampered with cookie does not validate but still exists. - /// let mut cookie = jar.get("private").unwrap().clone(); - /// jar.add(Cookie::new("private", cookie.value().to_string() + "!")); - /// assert!(jar.private(&key).get("private").is_none()); - /// assert!(jar.get("private").is_some()); - /// ``` - #[cfg(feature = "secure-cookies")] - pub fn private(&mut self, key: &Key) -> PrivateJar<'_> { - PrivateJar::new(self, key) - } - - /// Returns a `SignedJar` with `self` as its parent jar using the key `key` - /// to sign/verify cookies added/retrieved from the child jar. - /// - /// Any modifications to the child jar will be reflected on the parent jar, - /// and any retrievals from the child jar will be made from the parent jar. - /// - /// This method is only available when the `secure` feature is enabled. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{Cookie, CookieJar, Key}; - /// - /// // Generate a secure key. - /// let key = Key::generate(); - /// - /// // Add a signed cookie. - /// let mut jar = CookieJar::new(); - /// jar.signed(&key).add(Cookie::new("signed", "text")); - /// - /// // The cookie's contents are signed but still in plaintext. - /// assert_ne!(jar.get("signed").unwrap().value(), "text"); - /// assert!(jar.get("signed").unwrap().value().contains("text")); - /// - /// // They can be verified through the child jar. - /// assert_eq!(jar.signed(&key).get("signed").unwrap().value(), "text"); - /// - /// // A tampered with cookie does not validate but still exists. - /// let mut cookie = jar.get("signed").unwrap().clone(); - /// jar.add(Cookie::new("signed", cookie.value().to_string() + "!")); - /// assert!(jar.signed(&key).get("signed").is_none()); - /// assert!(jar.get("signed").is_some()); - /// ``` - #[cfg(feature = "secure-cookies")] - pub fn signed(&mut self, key: &Key) -> SignedJar<'_> { - SignedJar::new(self, key) - } -} - -use std::collections::hash_set::Iter as HashSetIter; - -/// Iterator over the changes to a cookie jar. -pub struct Delta<'a> { - iter: HashSetIter<'a, DeltaCookie>, -} - -impl<'a> Iterator for Delta<'a> { - type Item = &'a Cookie<'static>; - - fn next(&mut self) -> Option<&'a Cookie<'static>> { - self.iter.next().map(|c| &c.cookie) - } -} - -use std::collections::hash_map::RandomState; -use std::collections::hash_set::Difference; -use std::iter::Chain; - -/// Iterator over all of the cookies in a jar. -pub struct Iter<'a> { - delta_cookies: - Chain, Difference<'a, DeltaCookie, RandomState>>, -} - -impl<'a> Iterator for Iter<'a> { - type Item = &'a Cookie<'static>; - - fn next(&mut self) -> Option<&'a Cookie<'static>> { - for cookie in self.delta_cookies.by_ref() { - if !cookie.removed { - return Some(&*cookie); - } - } - - None - } -} - -#[cfg(test)] -mod test { - #[cfg(feature = "secure-cookies")] - use super::Key; - use super::{Cookie, CookieJar}; - - #[test] - #[allow(deprecated)] - fn simple() { - let mut c = CookieJar::new(); - - c.add(Cookie::new("test", "")); - c.add(Cookie::new("test2", "")); - c.remove(Cookie::named("test")); - - assert!(c.get("test").is_none()); - assert!(c.get("test2").is_some()); - - c.add(Cookie::new("test3", "")); - c.clear(); - - assert!(c.get("test").is_none()); - assert!(c.get("test2").is_none()); - assert!(c.get("test3").is_none()); - } - - #[test] - fn jar_is_send() { - fn is_send(_: T) -> bool { - true - } - - assert!(is_send(CookieJar::new())) - } - - #[test] - #[cfg(feature = "secure-cookies")] - fn iter() { - let key = Key::generate(); - let mut c = CookieJar::new(); - - c.add_original(Cookie::new("original", "original")); - - c.add(Cookie::new("test", "test")); - c.add(Cookie::new("test2", "test2")); - c.add(Cookie::new("test3", "test3")); - assert_eq!(c.iter().count(), 4); - - c.signed(&key).add(Cookie::new("signed", "signed")); - c.private(&key).add(Cookie::new("encrypted", "encrypted")); - assert_eq!(c.iter().count(), 6); - - c.remove(Cookie::named("test")); - assert_eq!(c.iter().count(), 5); - - c.remove(Cookie::named("signed")); - c.remove(Cookie::named("test2")); - assert_eq!(c.iter().count(), 3); - - c.add(Cookie::new("test2", "test2")); - assert_eq!(c.iter().count(), 4); - - c.remove(Cookie::named("test2")); - assert_eq!(c.iter().count(), 3); - } - - #[test] - #[cfg(feature = "secure-cookies")] - fn delta() { - use time::Duration; - use std::collections::HashMap; - - let mut c = CookieJar::new(); - - c.add_original(Cookie::new("original", "original")); - c.add_original(Cookie::new("original1", "original1")); - - c.add(Cookie::new("test", "test")); - c.add(Cookie::new("test2", "test2")); - c.add(Cookie::new("test3", "test3")); - c.add(Cookie::new("test4", "test4")); - - c.remove(Cookie::named("test")); - c.remove(Cookie::named("original")); - - assert_eq!(c.delta().count(), 4); - - let names: HashMap<_, _> = c.delta().map(|c| (c.name(), c.max_age())).collect(); - - assert!(names.get("test2").unwrap().is_none()); - assert!(names.get("test3").unwrap().is_none()); - assert!(names.get("test4").unwrap().is_none()); - assert_eq!(names.get("original").unwrap(), &Some(Duration::zero())); - } - - #[test] - fn replace_original() { - let mut jar = CookieJar::new(); - jar.add_original(Cookie::new("original_a", "a")); - jar.add_original(Cookie::new("original_b", "b")); - assert_eq!(jar.get("original_a").unwrap().value(), "a"); - - jar.add(Cookie::new("original_a", "av2")); - assert_eq!(jar.get("original_a").unwrap().value(), "av2"); - } - - #[test] - fn empty_delta() { - let mut jar = CookieJar::new(); - jar.add(Cookie::new("name", "val")); - assert_eq!(jar.delta().count(), 1); - - jar.remove(Cookie::named("name")); - assert_eq!(jar.delta().count(), 0); - - jar.add_original(Cookie::new("name", "val")); - assert_eq!(jar.delta().count(), 0); - - jar.remove(Cookie::named("name")); - assert_eq!(jar.delta().count(), 1); - - jar.add(Cookie::new("name", "val")); - assert_eq!(jar.delta().count(), 1); - - jar.remove(Cookie::named("name")); - assert_eq!(jar.delta().count(), 1); - } - - #[test] - fn add_remove_add() { - let mut jar = CookieJar::new(); - jar.add_original(Cookie::new("name", "val")); - assert_eq!(jar.delta().count(), 0); - - jar.remove(Cookie::named("name")); - assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1); - assert_eq!(jar.delta().count(), 1); - - // The cookie's been deleted. Another original doesn't change that. - jar.add_original(Cookie::new("name", "val")); - assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1); - assert_eq!(jar.delta().count(), 1); - - jar.remove(Cookie::named("name")); - assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1); - assert_eq!(jar.delta().count(), 1); - - jar.add(Cookie::new("name", "val")); - assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1); - assert_eq!(jar.delta().count(), 1); - - jar.remove(Cookie::named("name")); - assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1); - assert_eq!(jar.delta().count(), 1); - } - - #[test] - fn replace_remove() { - let mut jar = CookieJar::new(); - jar.add_original(Cookie::new("name", "val")); - assert_eq!(jar.delta().count(), 0); - - jar.add(Cookie::new("name", "val")); - assert_eq!(jar.delta().count(), 1); - assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1); - - jar.remove(Cookie::named("name")); - assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1); - } - - #[test] - fn remove_with_path() { - let mut jar = CookieJar::new(); - jar.add_original(Cookie::build("name", "val").finish()); - assert_eq!(jar.iter().count(), 1); - assert_eq!(jar.delta().count(), 0); - assert_eq!(jar.iter().filter(|c| c.path().is_none()).count(), 1); - - jar.remove(Cookie::build("name", "").path("/").finish()); - assert_eq!(jar.iter().count(), 0); - assert_eq!(jar.delta().count(), 1); - assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1); - assert_eq!(jar.delta().filter(|c| c.path() == Some("/")).count(), 1); - } -} diff --git a/actix-http/src/cookie/mod.rs b/actix-http/src/cookie/mod.rs deleted file mode 100644 index 09120e19f..000000000 --- a/actix-http/src/cookie/mod.rs +++ /dev/null @@ -1,1098 +0,0 @@ -//! https://github.com/alexcrichton/cookie-rs fork -//! -//! HTTP cookie parsing and cookie jar management. -//! -//! This crates provides the [`Cookie`](struct.Cookie.html) type, which directly -//! maps to an HTTP cookie, and the [`CookieJar`](struct.CookieJar.html) type, -//! which allows for simple management of many cookies as well as encryption and -//! signing of cookies for session management. -//! -//! # Features -//! -//! This crates can be configured at compile-time through the following Cargo -//! features: -//! -//! -//! * **secure** (disabled by default) -//! -//! Enables signed and private (signed + encrypted) cookie jars. -//! -//! When this feature is enabled, the -//! [signed](struct.CookieJar.html#method.signed) and -//! [private](struct.CookieJar.html#method.private) method of `CookieJar` and -//! [`SignedJar`](struct.SignedJar.html) and -//! [`PrivateJar`](struct.PrivateJar.html) structures are available. The jars -//! act as "children jars", allowing for easy retrieval and addition of signed -//! and/or encrypted cookies to a cookie jar. When this feature is disabled, -//! none of the types are available. -//! -//! * **percent-encode** (disabled by default) -//! -//! Enables percent encoding and decoding of names and values in cookies. -//! -//! When this feature is enabled, the -//! [encoded](struct.Cookie.html#method.encoded) and -//! [`parse_encoded`](struct.Cookie.html#method.parse_encoded) methods of -//! `Cookie` become available. The `encoded` method returns a wrapper around a -//! `Cookie` whose `Display` implementation percent-encodes the name and value -//! of the cookie. The `parse_encoded` method percent-decodes the name and -//! value of a `Cookie` during parsing. When this feature is disabled, the -//! `encoded` and `parse_encoded` methods are not available. -//! -//! You can enable features via the `Cargo.toml` file: -//! -//! ```ignore -//! [dependencies.cookie] -//! features = ["secure", "percent-encode"] -//! ``` - -#![doc(html_root_url = "https://docs.rs/cookie/0.11")] -#![deny(missing_docs)] - -mod builder; -mod delta; -mod draft; -mod jar; -mod parse; - -#[cfg(feature = "secure-cookies")] -#[macro_use] -mod secure; -#[cfg(feature = "secure-cookies")] -pub use self::secure::*; - -use std::borrow::Cow; -use std::fmt; -use std::str::FromStr; - -use percent_encoding::{percent_encode, AsciiSet, CONTROLS}; -use time::{Duration, OffsetDateTime}; - -pub use self::builder::CookieBuilder; -pub use self::draft::*; -pub use self::jar::{CookieJar, Delta, Iter}; -use self::parse::parse_cookie; -pub use self::parse::ParseError; - -/// https://url.spec.whatwg.org/#fragment-percent-encode-set -const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`'); - -/// https://url.spec.whatwg.org/#path-percent-encode-set -const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}'); - -/// https://url.spec.whatwg.org/#userinfo-percent-encode-set -pub const USERINFO: &AsciiSet = &PATH - .add(b'/') - .add(b':') - .add(b';') - .add(b'=') - .add(b'@') - .add(b'[') - .add(b'\\') - .add(b']') - .add(b'^') - .add(b'|'); - -#[derive(Debug, Clone)] -enum CookieStr { - /// An string derived from indexes (start, end). - Indexed(usize, usize), - /// A string derived from a concrete string. - Concrete(Cow<'static, str>), -} - -impl CookieStr { - /// Retrieves the string `self` corresponds to. If `self` is derived from - /// indexes, the corresponding subslice of `string` is returned. Otherwise, - /// the concrete string is returned. - /// - /// # Panics - /// - /// Panics if `self` is an indexed string and `string` is None. - fn to_str<'s>(&'s self, string: Option<&'s Cow<'_, str>>) -> &'s str { - match *self { - CookieStr::Indexed(i, j) => { - let s = string.expect( - "`Some` base string must exist when \ - converting indexed str to str! (This is a module invariant.)", - ); - &s[i..j] - } - CookieStr::Concrete(ref cstr) => &*cstr, - } - } - - #[allow(clippy::ptr_arg)] - fn to_raw_str<'s, 'c: 's>(&'s self, string: &'s Cow<'c, str>) -> Option<&'c str> { - match *self { - CookieStr::Indexed(i, j) => match *string { - Cow::Borrowed(s) => Some(&s[i..j]), - Cow::Owned(_) => None, - }, - CookieStr::Concrete(_) => None, - } - } -} - -/// Representation of an HTTP cookie. -/// -/// # Constructing a `Cookie` -/// -/// To construct a cookie with only a name/value, use the [new](#method.new) -/// method: -/// -/// ```rust -/// use actix_http::cookie::Cookie; -/// -/// let cookie = Cookie::new("name", "value"); -/// assert_eq!(&cookie.to_string(), "name=value"); -/// ``` -/// -/// To construct more elaborate cookies, use the [build](#method.build) method -/// and [`CookieBuilder`](struct.CookieBuilder.html) methods: -/// -/// ```rust -/// use actix_http::cookie::Cookie; -/// -/// let cookie = Cookie::build("name", "value") -/// .domain("www.rust-lang.org") -/// .path("/") -/// .secure(true) -/// .http_only(true) -/// .finish(); -/// ``` -#[derive(Debug, Clone)] -pub struct Cookie<'c> { - /// Storage for the cookie string. Only used if this structure was derived - /// from a string that was subsequently parsed. - cookie_string: Option>, - /// The cookie's name. - name: CookieStr, - /// The cookie's value. - value: CookieStr, - /// The cookie's expiration, if any. - expires: Option, - /// The cookie's maximum age, if any. - max_age: Option, - /// The cookie's domain, if any. - domain: Option, - /// The cookie's path domain, if any. - path: Option, - /// Whether this cookie was marked Secure. - secure: Option, - /// Whether this cookie was marked HttpOnly. - http_only: Option, - /// The draft `SameSite` attribute. - same_site: Option, -} - -impl Cookie<'static> { - /// Creates a new `Cookie` with the given name and value. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let cookie = Cookie::new("name", "value"); - /// assert_eq!(cookie.name_value(), ("name", "value")); - /// ``` - pub fn new(name: N, value: V) -> Cookie<'static> - where - N: Into>, - V: Into>, - { - Cookie { - cookie_string: None, - name: CookieStr::Concrete(name.into()), - value: CookieStr::Concrete(value.into()), - expires: None, - max_age: None, - domain: None, - path: None, - secure: None, - http_only: None, - same_site: None, - } - } - - /// Creates a new `Cookie` with the given name and an empty value. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let cookie = Cookie::named("name"); - /// assert_eq!(cookie.name(), "name"); - /// assert!(cookie.value().is_empty()); - /// ``` - pub fn named(name: N) -> Cookie<'static> - where - N: Into>, - { - Cookie::new(name, "") - } - - /// Creates a new `CookieBuilder` instance from the given key and value - /// strings. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::build("foo", "bar").finish(); - /// assert_eq!(c.name_value(), ("foo", "bar")); - /// ``` - pub fn build(name: N, value: V) -> CookieBuilder - where - N: Into>, - V: Into>, - { - CookieBuilder::new(name, value) - } -} - -impl<'c> Cookie<'c> { - /// Parses a `Cookie` from the given HTTP cookie header value string. Does - /// not perform any percent-decoding. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::parse("foo=bar%20baz; HttpOnly").unwrap(); - /// assert_eq!(c.name_value(), ("foo", "bar%20baz")); - /// assert_eq!(c.http_only(), Some(true)); - /// ``` - pub fn parse(s: S) -> Result, ParseError> - where - S: Into>, - { - parse_cookie(s, false) - } - - /// Parses a `Cookie` from the given HTTP cookie header value string where - /// the name and value fields are percent-encoded. Percent-decodes the - /// name/value fields. - /// - /// This API requires the `percent-encode` feature to be enabled on this - /// crate. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::parse_encoded("foo=bar%20baz; HttpOnly").unwrap(); - /// assert_eq!(c.name_value(), ("foo", "bar baz")); - /// assert_eq!(c.http_only(), Some(true)); - /// ``` - pub fn parse_encoded(s: S) -> Result, ParseError> - where - S: Into>, - { - parse_cookie(s, true) - } - - /// Wraps `self` in an `EncodedCookie`: a cost-free wrapper around `Cookie` - /// whose `Display` implementation percent-encodes the name and value of the - /// wrapped `Cookie`. - /// - /// This method is only available when the `percent-encode` feature is - /// enabled. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let mut c = Cookie::new("my name", "this; value?"); - /// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F"); - /// ``` - pub fn encoded<'a>(&'a self) -> EncodedCookie<'a, 'c> { - EncodedCookie(self) - } - - /// Converts `self` into a `Cookie` with a static lifetime. This method - /// results in at most one allocation. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::new("a", "b"); - /// let owned_cookie = c.into_owned(); - /// assert_eq!(owned_cookie.name_value(), ("a", "b")); - /// ``` - pub fn into_owned(self) -> Cookie<'static> { - Cookie { - cookie_string: self.cookie_string.map(|s| s.into_owned().into()), - name: self.name, - value: self.value, - expires: self.expires, - max_age: self.max_age, - domain: self.domain, - path: self.path, - secure: self.secure, - http_only: self.http_only, - same_site: self.same_site, - } - } - - /// Returns the name of `self`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::new("name", "value"); - /// assert_eq!(c.name(), "name"); - /// ``` - #[inline] - pub fn name(&self) -> &str { - self.name.to_str(self.cookie_string.as_ref()) - } - - /// Returns the value of `self`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::new("name", "value"); - /// assert_eq!(c.value(), "value"); - /// ``` - #[inline] - pub fn value(&self) -> &str { - self.value.to_str(self.cookie_string.as_ref()) - } - - /// Returns the name and value of `self` as a tuple of `(name, value)`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::new("name", "value"); - /// assert_eq!(c.name_value(), ("name", "value")); - /// ``` - #[inline] - pub fn name_value(&self) -> (&str, &str) { - (self.name(), self.value()) - } - - /// Returns whether this cookie was marked `HttpOnly` or not. Returns - /// `Some(true)` when the cookie was explicitly set (manually or parsed) as - /// `HttpOnly`, `Some(false)` when `http_only` was manually set to `false`, - /// and `None` otherwise. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::parse("name=value; httponly").unwrap(); - /// assert_eq!(c.http_only(), Some(true)); - /// - /// let mut c = Cookie::new("name", "value"); - /// assert_eq!(c.http_only(), None); - /// - /// let mut c = Cookie::new("name", "value"); - /// assert_eq!(c.http_only(), None); - /// - /// // An explicitly set "false" value. - /// c.set_http_only(false); - /// assert_eq!(c.http_only(), Some(false)); - /// - /// // An explicitly set "true" value. - /// c.set_http_only(true); - /// assert_eq!(c.http_only(), Some(true)); - /// ``` - #[inline] - pub fn http_only(&self) -> Option { - self.http_only - } - - /// Returns whether this cookie was marked `Secure` or not. Returns - /// `Some(true)` when the cookie was explicitly set (manually or parsed) as - /// `Secure`, `Some(false)` when `secure` was manually set to `false`, and - /// `None` otherwise. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::parse("name=value; Secure").unwrap(); - /// assert_eq!(c.secure(), Some(true)); - /// - /// let mut c = Cookie::parse("name=value").unwrap(); - /// assert_eq!(c.secure(), None); - /// - /// let mut c = Cookie::new("name", "value"); - /// assert_eq!(c.secure(), None); - /// - /// // An explicitly set "false" value. - /// c.set_secure(false); - /// assert_eq!(c.secure(), Some(false)); - /// - /// // An explicitly set "true" value. - /// c.set_secure(true); - /// assert_eq!(c.secure(), Some(true)); - /// ``` - #[inline] - pub fn secure(&self) -> Option { - self.secure - } - - /// Returns the `SameSite` attribute of this cookie if one was specified. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{Cookie, SameSite}; - /// - /// let c = Cookie::parse("name=value; SameSite=Lax").unwrap(); - /// assert_eq!(c.same_site(), Some(SameSite::Lax)); - /// ``` - #[inline] - pub fn same_site(&self) -> Option { - self.same_site - } - - /// Returns the specified max-age of the cookie if one was specified. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::parse("name=value").unwrap(); - /// assert_eq!(c.max_age(), None); - /// - /// let c = Cookie::parse("name=value; Max-Age=3600").unwrap(); - /// assert_eq!(c.max_age().map(|age| age.whole_hours()), Some(1)); - /// ``` - #[inline] - pub fn max_age(&self) -> Option { - self.max_age - } - - /// Returns the `Path` of the cookie if one was specified. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::parse("name=value").unwrap(); - /// assert_eq!(c.path(), None); - /// - /// let c = Cookie::parse("name=value; Path=/").unwrap(); - /// assert_eq!(c.path(), Some("/")); - /// - /// let c = Cookie::parse("name=value; path=/sub").unwrap(); - /// assert_eq!(c.path(), Some("/sub")); - /// ``` - #[inline] - pub fn path(&self) -> Option<&str> { - match self.path { - Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())), - None => None, - } - } - - /// Returns the `Domain` of the cookie if one was specified. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::parse("name=value").unwrap(); - /// assert_eq!(c.domain(), None); - /// - /// let c = Cookie::parse("name=value; Domain=crates.io").unwrap(); - /// assert_eq!(c.domain(), Some("crates.io")); - /// ``` - #[inline] - pub fn domain(&self) -> Option<&str> { - match self.domain { - Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())), - None => None, - } - } - - /// Returns the `Expires` time of the cookie if one was specified. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let c = Cookie::parse("name=value").unwrap(); - /// assert_eq!(c.expires(), None); - /// - /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT"; - /// let cookie_str = format!("name=value; Expires={}", expire_time); - /// let c = Cookie::parse(cookie_str).unwrap(); - /// assert_eq!(c.expires().map(|t| t.year()), Some(2017)); - /// ``` - #[inline] - pub fn expires(&self) -> Option { - self.expires - } - - /// Sets the name of `self` to `name`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let mut c = Cookie::new("name", "value"); - /// assert_eq!(c.name(), "name"); - /// - /// c.set_name("foo"); - /// assert_eq!(c.name(), "foo"); - /// ``` - pub fn set_name>>(&mut self, name: N) { - self.name = CookieStr::Concrete(name.into()) - } - - /// Sets the value of `self` to `value`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let mut c = Cookie::new("name", "value"); - /// assert_eq!(c.value(), "value"); - /// - /// c.set_value("bar"); - /// assert_eq!(c.value(), "bar"); - /// ``` - pub fn set_value>>(&mut self, value: V) { - self.value = CookieStr::Concrete(value.into()) - } - - /// Sets the value of `http_only` in `self` to `value`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let mut c = Cookie::new("name", "value"); - /// assert_eq!(c.http_only(), None); - /// - /// c.set_http_only(true); - /// assert_eq!(c.http_only(), Some(true)); - /// ``` - #[inline] - pub fn set_http_only(&mut self, value: bool) { - self.http_only = Some(value); - } - - /// Sets the value of `secure` in `self` to `value`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let mut c = Cookie::new("name", "value"); - /// assert_eq!(c.secure(), None); - /// - /// c.set_secure(true); - /// assert_eq!(c.secure(), Some(true)); - /// ``` - #[inline] - pub fn set_secure(&mut self, value: bool) { - self.secure = Some(value); - } - - /// Sets the value of `same_site` in `self` to `value`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{Cookie, SameSite}; - /// - /// let mut c = Cookie::new("name", "value"); - /// assert!(c.same_site().is_none()); - /// - /// c.set_same_site(SameSite::Strict); - /// assert_eq!(c.same_site(), Some(SameSite::Strict)); - /// ``` - #[inline] - pub fn set_same_site(&mut self, value: SameSite) { - self.same_site = Some(value); - } - - /// Sets the value of `max_age` in `self` to `value`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// use time::Duration; - /// - /// let mut c = Cookie::new("name", "value"); - /// assert_eq!(c.max_age(), None); - /// - /// c.set_max_age(Duration::hours(10)); - /// assert_eq!(c.max_age(), Some(Duration::hours(10))); - /// ``` - #[inline] - pub fn set_max_age(&mut self, value: Duration) { - self.max_age = Some(value); - } - - /// Sets the `path` of `self` to `path`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let mut c = Cookie::new("name", "value"); - /// assert_eq!(c.path(), None); - /// - /// c.set_path("/"); - /// assert_eq!(c.path(), Some("/")); - /// ``` - pub fn set_path>>(&mut self, path: P) { - self.path = Some(CookieStr::Concrete(path.into())); - } - - /// Sets the `domain` of `self` to `domain`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let mut c = Cookie::new("name", "value"); - /// assert_eq!(c.domain(), None); - /// - /// c.set_domain("rust-lang.org"); - /// assert_eq!(c.domain(), Some("rust-lang.org")); - /// ``` - pub fn set_domain>>(&mut self, domain: D) { - self.domain = Some(CookieStr::Concrete(domain.into())); - } - - /// Sets the expires field of `self` to `time`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// use time::{Duration, OffsetDateTime}; - /// - /// let mut c = Cookie::new("name", "value"); - /// assert_eq!(c.expires(), None); - /// - /// let mut now = OffsetDateTime::now(); - /// now += Duration::week(); - /// - /// c.set_expires(now); - /// assert!(c.expires().is_some()) - /// ``` - #[inline] - pub fn set_expires(&mut self, time: OffsetDateTime) { - self.expires = Some(time); - } - - /// Makes `self` a "permanent" cookie by extending its expiration and max - /// age 20 years into the future. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// use time::Duration; - /// - /// let mut c = Cookie::new("foo", "bar"); - /// assert!(c.expires().is_none()); - /// assert!(c.max_age().is_none()); - /// - /// c.make_permanent(); - /// assert!(c.expires().is_some()); - /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20))); - /// ``` - pub fn make_permanent(&mut self) { - let twenty_years = Duration::days(365 * 20); - self.set_max_age(twenty_years); - self.set_expires(OffsetDateTime::now() + twenty_years); - } - - fn fmt_parameters(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(true) = self.http_only() { - write!(f, "; HttpOnly")?; - } - - if let Some(true) = self.secure() { - write!(f, "; Secure")?; - } - - if let Some(same_site) = self.same_site() { - write!(f, "; SameSite={}", same_site)?; - } - - if let Some(path) = self.path() { - write!(f, "; Path={}", path)?; - } - - if let Some(domain) = self.domain() { - write!(f, "; Domain={}", domain)?; - } - - if let Some(max_age) = self.max_age() { - write!(f, "; Max-Age={}", max_age.whole_seconds())?; - } - - if let Some(time) = self.expires() { - write!(f, "; Expires={}", time.format("%a, %d %b %Y %H:%M:%S GMT"))?; - } - - Ok(()) - } - - /// Returns the name of `self` as a string slice of the raw string `self` - /// was originally parsed from. If `self` was not originally parsed from a - /// raw string, returns `None`. - /// - /// This method differs from [name](#method.name) in that it returns a - /// string with the same lifetime as the originally parsed string. This - /// lifetime may outlive `self`. If a longer lifetime is not required, or - /// you're unsure if you need a longer lifetime, use [name](#method.name). - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let cookie_string = format!("{}={}", "foo", "bar"); - /// - /// // `c` will be dropped at the end of the scope, but `name` will live on - /// let name = { - /// let c = Cookie::parse(cookie_string.as_str()).unwrap(); - /// c.name_raw() - /// }; - /// - /// assert_eq!(name, Some("foo")); - /// ``` - #[inline] - pub fn name_raw(&self) -> Option<&'c str> { - self.cookie_string - .as_ref() - .and_then(|s| self.name.to_raw_str(s)) - } - - /// Returns the value of `self` as a string slice of the raw string `self` - /// was originally parsed from. If `self` was not originally parsed from a - /// raw string, returns `None`. - /// - /// This method differs from [value](#method.value) in that it returns a - /// string with the same lifetime as the originally parsed string. This - /// lifetime may outlive `self`. If a longer lifetime is not required, or - /// you're unsure if you need a longer lifetime, use [value](#method.value). - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let cookie_string = format!("{}={}", "foo", "bar"); - /// - /// // `c` will be dropped at the end of the scope, but `value` will live on - /// let value = { - /// let c = Cookie::parse(cookie_string.as_str()).unwrap(); - /// c.value_raw() - /// }; - /// - /// assert_eq!(value, Some("bar")); - /// ``` - #[inline] - pub fn value_raw(&self) -> Option<&'c str> { - self.cookie_string - .as_ref() - .and_then(|s| self.value.to_raw_str(s)) - } - - /// Returns the `Path` of `self` as a string slice of the raw string `self` - /// was originally parsed from. If `self` was not originally parsed from a - /// raw string, or if `self` doesn't contain a `Path`, or if the `Path` has - /// changed since parsing, returns `None`. - /// - /// This method differs from [path](#method.path) in that it returns a - /// string with the same lifetime as the originally parsed string. This - /// lifetime may outlive `self`. If a longer lifetime is not required, or - /// you're unsure if you need a longer lifetime, use [path](#method.path). - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let cookie_string = format!("{}={}; Path=/", "foo", "bar"); - /// - /// // `c` will be dropped at the end of the scope, but `path` will live on - /// let path = { - /// let c = Cookie::parse(cookie_string.as_str()).unwrap(); - /// c.path_raw() - /// }; - /// - /// assert_eq!(path, Some("/")); - /// ``` - #[inline] - pub fn path_raw(&self) -> Option<&'c str> { - match (self.path.as_ref(), self.cookie_string.as_ref()) { - (Some(path), Some(string)) => path.to_raw_str(string), - _ => None, - } - } - - /// Returns the `Domain` of `self` as a string slice of the raw string - /// `self` was originally parsed from. If `self` was not originally parsed - /// from a raw string, or if `self` doesn't contain a `Domain`, or if the - /// `Domain` has changed since parsing, returns `None`. - /// - /// This method differs from [domain](#method.domain) in that it returns a - /// string with the same lifetime as the originally parsed string. This - /// lifetime may outlive `self` struct. If a longer lifetime is not - /// required, or you're unsure if you need a longer lifetime, use - /// [domain](#method.domain). - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let cookie_string = format!("{}={}; Domain=crates.io", "foo", "bar"); - /// - /// //`c` will be dropped at the end of the scope, but `domain` will live on - /// let domain = { - /// let c = Cookie::parse(cookie_string.as_str()).unwrap(); - /// c.domain_raw() - /// }; - /// - /// assert_eq!(domain, Some("crates.io")); - /// ``` - #[inline] - pub fn domain_raw(&self) -> Option<&'c str> { - match (self.domain.as_ref(), self.cookie_string.as_ref()) { - (Some(domain), Some(string)) => domain.to_raw_str(string), - _ => None, - } - } -} - -/// Wrapper around `Cookie` whose `Display` implementation percent-encodes the -/// cookie's name and value. -/// -/// A value of this type can be obtained via the -/// [encoded](struct.Cookie.html#method.encoded) method on -/// [Cookie](struct.Cookie.html). This type should only be used for its -/// `Display` implementation. -/// -/// This type is only available when the `percent-encode` feature is enabled. -/// -/// # Example -/// -/// ```rust -/// use actix_http::cookie::Cookie; -/// -/// let mut c = Cookie::new("my name", "this; value?"); -/// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F"); -/// ``` -pub struct EncodedCookie<'a, 'c>(&'a Cookie<'c>); - -impl<'a, 'c: 'a> fmt::Display for EncodedCookie<'a, 'c> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Percent-encode the name and value. - let name = percent_encode(self.0.name().as_bytes(), USERINFO); - let value = percent_encode(self.0.value().as_bytes(), USERINFO); - - // Write out the name/value pair and the cookie's parameters. - write!(f, "{}={}", name, value)?; - self.0.fmt_parameters(f) - } -} - -impl<'c> fmt::Display for Cookie<'c> { - /// Formats the cookie `self` as a `Set-Cookie` header value. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Cookie; - /// - /// let mut cookie = Cookie::build("foo", "bar") - /// .path("/") - /// .finish(); - /// - /// assert_eq!(&cookie.to_string(), "foo=bar; Path=/"); - /// ``` - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}={}", self.name(), self.value())?; - self.fmt_parameters(f) - } -} - -impl FromStr for Cookie<'static> { - type Err = ParseError; - - fn from_str(s: &str) -> Result, ParseError> { - Cookie::parse(s).map(|c| c.into_owned()) - } -} - -impl<'a, 'b> PartialEq> for Cookie<'a> { - fn eq(&self, other: &Cookie<'b>) -> bool { - let so_far_so_good = self.name() == other.name() - && self.value() == other.value() - && self.http_only() == other.http_only() - && self.secure() == other.secure() - && self.max_age() == other.max_age() - && self.expires() == other.expires(); - - if !so_far_so_good { - return false; - } - - match (self.path(), other.path()) { - (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {} - (None, None) => {} - _ => return false, - }; - - match (self.domain(), other.domain()) { - (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {} - (None, None) => {} - _ => return false, - }; - - true - } -} - -#[cfg(test)] -mod tests { - use super::{Cookie, SameSite}; - use time::{offset, PrimitiveDateTime}; - - #[test] - fn format() { - let cookie = Cookie::new("foo", "bar"); - assert_eq!(&cookie.to_string(), "foo=bar"); - - let cookie = Cookie::build("foo", "bar").http_only(true).finish(); - assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly"); - - let cookie = Cookie::build("foo", "bar").max_age(10).finish(); - assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10"); - - let cookie = Cookie::build("foo", "bar").secure(true).finish(); - assert_eq!(&cookie.to_string(), "foo=bar; Secure"); - - let cookie = Cookie::build("foo", "bar").path("/").finish(); - assert_eq!(&cookie.to_string(), "foo=bar; Path=/"); - - let cookie = Cookie::build("foo", "bar") - .domain("www.rust-lang.org") - .finish(); - assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org"); - - let time_str = "Wed, 21 Oct 2015 07:28:00 GMT"; - let expires = PrimitiveDateTime::parse(time_str, "%a, %d %b %Y %H:%M:%S").unwrap().using_offset(offset!(UTC)); - let cookie = Cookie::build("foo", "bar").expires(expires).finish(); - assert_eq!( - &cookie.to_string(), - "foo=bar; Expires=Wed, 21 Oct 2015 07:28:00 GMT" - ); - - let cookie = Cookie::build("foo", "bar") - .same_site(SameSite::Strict) - .finish(); - assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Strict"); - - let cookie = Cookie::build("foo", "bar") - .same_site(SameSite::Lax) - .finish(); - assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Lax"); - - let cookie = Cookie::build("foo", "bar") - .same_site(SameSite::None) - .finish(); - assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None"); - } - - #[test] - fn cookie_string_long_lifetimes() { - let cookie_string = - "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned(); - let (name, value, path, domain) = { - // Create a cookie passing a slice - let c = Cookie::parse(cookie_string.as_str()).unwrap(); - (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw()) - }; - - assert_eq!(name, Some("bar")); - assert_eq!(value, Some("baz")); - assert_eq!(path, Some("/subdir")); - assert_eq!(domain, Some("crates.io")); - } - - #[test] - fn owned_cookie_string() { - let cookie_string = - "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned(); - let (name, value, path, domain) = { - // Create a cookie passing an owned string - let c = Cookie::parse(cookie_string).unwrap(); - (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw()) - }; - - assert_eq!(name, None); - assert_eq!(value, None); - assert_eq!(path, None); - assert_eq!(domain, None); - } - - #[test] - fn owned_cookie_struct() { - let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io"; - let (name, value, path, domain) = { - // Create an owned cookie - let c = Cookie::parse(cookie_string).unwrap().into_owned(); - - (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw()) - }; - - assert_eq!(name, None); - assert_eq!(value, None); - assert_eq!(path, None); - assert_eq!(domain, None); - } - - #[test] - fn format_encoded() { - let cookie = Cookie::build("foo !?=", "bar;; a").finish(); - let cookie_str = cookie.encoded().to_string(); - assert_eq!(&cookie_str, "foo%20!%3F%3D=bar%3B%3B%20a"); - - let cookie = Cookie::parse_encoded(cookie_str).unwrap(); - assert_eq!(cookie.name_value(), ("foo !?=", "bar;; a")); - } -} diff --git a/actix-http/src/cookie/parse.rs b/actix-http/src/cookie/parse.rs deleted file mode 100644 index 28eb4f8b6..000000000 --- a/actix-http/src/cookie/parse.rs +++ /dev/null @@ -1,421 +0,0 @@ -use std::borrow::Cow; -use std::cmp; -use std::convert::From; -use std::error::Error; -use std::fmt; -use std::str::Utf8Error; - -use percent_encoding::percent_decode; -use time::{Duration, offset}; - -use super::{Cookie, CookieStr, SameSite}; - -use crate::time_parser; - -/// Enum corresponding to a parsing error. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum ParseError { - /// The cookie did not contain a name/value pair. - MissingPair, - /// The cookie's name was empty. - EmptyName, - /// Decoding the cookie's name or value resulted in invalid UTF-8. - Utf8Error(Utf8Error), - /// It is discouraged to exhaustively match on this enum as its variants may - /// grow without a breaking-change bump in version numbers. - #[doc(hidden)] - __Nonexhasutive, -} - -impl ParseError { - /// Returns a description of this error as a string - pub fn as_str(&self) -> &'static str { - match *self { - ParseError::MissingPair => "the cookie is missing a name/value pair", - ParseError::EmptyName => "the cookie's name is empty", - ParseError::Utf8Error(_) => { - "decoding the cookie's name or value resulted in invalid UTF-8" - } - ParseError::__Nonexhasutive => unreachable!("__Nonexhasutive ParseError"), - } - } -} - -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -impl From for ParseError { - fn from(error: Utf8Error) -> ParseError { - ParseError::Utf8Error(error) - } -} - -impl Error for ParseError {} - -fn indexes_of(needle: &str, haystack: &str) -> Option<(usize, usize)> { - let haystack_start = haystack.as_ptr() as usize; - let needle_start = needle.as_ptr() as usize; - - if needle_start < haystack_start { - return None; - } - - if (needle_start + needle.len()) > (haystack_start + haystack.len()) { - return None; - } - - let start = needle_start - haystack_start; - let end = start + needle.len(); - Some((start, end)) -} - -fn name_val_decoded( - name: &str, - val: &str, -) -> Result<(CookieStr, CookieStr), ParseError> { - let decoded_name = percent_decode(name.as_bytes()).decode_utf8()?; - let decoded_value = percent_decode(val.as_bytes()).decode_utf8()?; - let name = CookieStr::Concrete(Cow::Owned(decoded_name.into_owned())); - let val = CookieStr::Concrete(Cow::Owned(decoded_value.into_owned())); - - Ok((name, val)) -} - -// This function does the real parsing but _does not_ set the `cookie_string` in -// the returned cookie object. This only exists so that the borrow to `s` is -// returned at the end of the call, allowing the `cookie_string` field to be -// set in the outer `parse` function. -fn parse_inner<'c>(s: &str, decode: bool) -> Result, ParseError> { - let mut attributes = s.split(';'); - let key_value = match attributes.next() { - Some(s) => s, - _ => panic!(), - }; - - // Determine the name = val. - let (name, value) = match key_value.find('=') { - Some(i) => (key_value[..i].trim(), key_value[(i + 1)..].trim()), - None => return Err(ParseError::MissingPair), - }; - - if name.is_empty() { - return Err(ParseError::EmptyName); - } - - // Create a cookie with all of the defaults. We'll fill things in while we - // iterate through the parameters below. - let (name, value) = if decode { - name_val_decoded(name, value)? - } else { - let name_indexes = indexes_of(name, s).expect("name sub"); - let value_indexes = indexes_of(value, s).expect("value sub"); - let name = CookieStr::Indexed(name_indexes.0, name_indexes.1); - let value = CookieStr::Indexed(value_indexes.0, value_indexes.1); - - (name, value) - }; - - let mut cookie = Cookie { - name, - value, - cookie_string: None, - expires: None, - max_age: None, - domain: None, - path: None, - secure: None, - http_only: None, - same_site: None, - }; - - for attr in attributes { - let (key, value) = match attr.find('=') { - Some(i) => (attr[..i].trim(), Some(attr[(i + 1)..].trim())), - None => (attr.trim(), None), - }; - - match (&*key.to_ascii_lowercase(), value) { - ("secure", _) => cookie.secure = Some(true), - ("httponly", _) => cookie.http_only = Some(true), - ("max-age", Some(v)) => { - // See RFC 6265 Section 5.2.2, negative values indicate that the - // earliest possible expiration time should be used, so set the - // max age as 0 seconds. - cookie.max_age = match v.parse() { - Ok(val) if val <= 0 => Some(Duration::zero()), - Ok(val) => { - // Don't panic if the max age seconds is greater than what's supported by - // `Duration`. - let val = cmp::min(val, Duration::max_value().whole_seconds()); - Some(Duration::seconds(val)) - } - Err(_) => continue, - }; - } - ("domain", Some(mut domain)) if !domain.is_empty() => { - if domain.starts_with('.') { - domain = &domain[1..]; - } - - let (i, j) = indexes_of(domain, s).expect("domain sub"); - cookie.domain = Some(CookieStr::Indexed(i, j)); - } - ("path", Some(v)) => { - let (i, j) = indexes_of(v, s).expect("path sub"); - cookie.path = Some(CookieStr::Indexed(i, j)); - } - ("samesite", Some(v)) => { - if v.eq_ignore_ascii_case("strict") { - cookie.same_site = Some(SameSite::Strict); - } else if v.eq_ignore_ascii_case("lax") { - cookie.same_site = Some(SameSite::Lax); - } else { - // We do nothing here, for now. When/if the `SameSite` - // attribute becomes standard, the spec says that we should - // ignore this cookie, i.e, fail to parse it, when an - // invalid value is passed in. The draft is at - // http://httpwg.org/http-extensions/draft-ietf-httpbis-cookie-same-site.html. - } - } - ("expires", Some(v)) => { - // Try parsing with three date formats according to - // http://tools.ietf.org/html/rfc2616#section-3.3.1. Try - // additional ones as encountered in the real world. - let tm = time_parser::parse_http_date(v) - .or_else(|| time::parse(v, "%a, %d-%b-%Y %H:%M:%S").ok()); - - if let Some(time) = tm { - cookie.expires = Some(time.using_offset(offset!(UTC))) - } - } - _ => { - // We're going to be permissive here. If we have no idea what - // this is, then it's something nonstandard. We're not going to - // store it (because it's not compliant), but we're also not - // going to emit an error. - } - } - } - - Ok(cookie) -} - -pub fn parse_cookie<'c, S>(cow: S, decode: bool) -> Result, ParseError> -where - S: Into>, -{ - let s = cow.into(); - let mut cookie = parse_inner(&s, decode)?; - cookie.cookie_string = Some(s); - Ok(cookie) -} - -#[cfg(test)] -mod tests { - use super::{Cookie, SameSite}; - use time::{offset, Duration, PrimitiveDateTime}; - - macro_rules! assert_eq_parse { - ($string:expr, $expected:expr) => { - let cookie = match Cookie::parse($string) { - Ok(cookie) => cookie, - Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e), - }; - - assert_eq!(cookie, $expected); - }; - } - - macro_rules! assert_ne_parse { - ($string:expr, $expected:expr) => { - let cookie = match Cookie::parse($string) { - Ok(cookie) => cookie, - Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e), - }; - - assert_ne!(cookie, $expected); - }; - } - - #[test] - fn parse_same_site() { - let expected = Cookie::build("foo", "bar") - .same_site(SameSite::Lax) - .finish(); - - assert_eq_parse!("foo=bar; SameSite=Lax", expected); - assert_eq_parse!("foo=bar; SameSite=lax", expected); - assert_eq_parse!("foo=bar; SameSite=LAX", expected); - assert_eq_parse!("foo=bar; samesite=Lax", expected); - assert_eq_parse!("foo=bar; SAMESITE=Lax", expected); - - let expected = Cookie::build("foo", "bar") - .same_site(SameSite::Strict) - .finish(); - - assert_eq_parse!("foo=bar; SameSite=Strict", expected); - assert_eq_parse!("foo=bar; SameSITE=Strict", expected); - assert_eq_parse!("foo=bar; SameSite=strict", expected); - assert_eq_parse!("foo=bar; SameSite=STrICT", expected); - assert_eq_parse!("foo=bar; SameSite=STRICT", expected); - } - - #[test] - fn parse() { - assert!(Cookie::parse("bar").is_err()); - assert!(Cookie::parse("=bar").is_err()); - assert!(Cookie::parse(" =bar").is_err()); - assert!(Cookie::parse("foo=").is_ok()); - - let expected = Cookie::build("foo", "bar=baz").finish(); - assert_eq_parse!("foo=bar=baz", expected); - - let mut expected = Cookie::build("foo", "bar").finish(); - assert_eq_parse!("foo=bar", expected); - assert_eq_parse!("foo = bar", expected); - assert_eq_parse!(" foo=bar ", expected); - assert_eq_parse!(" foo=bar ;Domain=", expected); - assert_eq_parse!(" foo=bar ;Domain= ", expected); - assert_eq_parse!(" foo=bar ;Ignored", expected); - - let mut unexpected = Cookie::build("foo", "bar").http_only(false).finish(); - assert_ne_parse!(" foo=bar ;HttpOnly", unexpected); - assert_ne_parse!(" foo=bar; httponly", unexpected); - - expected.set_http_only(true); - assert_eq_parse!(" foo=bar ;HttpOnly", expected); - assert_eq_parse!(" foo=bar ;httponly", expected); - assert_eq_parse!(" foo=bar ;HTTPONLY=whatever", expected); - assert_eq_parse!(" foo=bar ; sekure; HTTPONLY", expected); - - expected.set_secure(true); - assert_eq_parse!(" foo=bar ;HttpOnly; Secure", expected); - assert_eq_parse!(" foo=bar ;HttpOnly; Secure=aaaa", expected); - - unexpected.set_http_only(true); - unexpected.set_secure(true); - assert_ne_parse!(" foo=bar ;HttpOnly; skeure", unexpected); - assert_ne_parse!(" foo=bar ;HttpOnly; =secure", unexpected); - assert_ne_parse!(" foo=bar ;HttpOnly;", unexpected); - - unexpected.set_secure(false); - assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected); - assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected); - assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected); - - expected.set_max_age(Duration::zero()); - assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=0", expected); - assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0 ", expected); - assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=-1", expected); - assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", expected); - - expected.set_max_age(Duration::minutes(1)); - assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=60", expected); - assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 60 ", expected); - - expected.set_max_age(Duration::seconds(4)); - assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4", expected); - assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 4 ", expected); - - unexpected.set_secure(true); - unexpected.set_max_age(Duration::minutes(1)); - assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=122", unexpected); - assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 38 ", unexpected); - assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=51", unexpected); - assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", unexpected); - assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0", unexpected); - - expected.set_path("/"); - assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/", expected); - assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/", expected); - - expected.set_path("/foo"); - assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", expected); - assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/foo", expected); - assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path=/foo", expected); - assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path = /foo", expected); - - unexpected.set_max_age(Duration::seconds(4)); - unexpected.set_path("/bar"); - assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", unexpected); - assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/baz", unexpected); - - expected.set_domain("www.foo.com"); - assert_eq_parse!( - " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ - Domain=www.foo.com", - expected - ); - - expected.set_domain("foo.com"); - assert_eq_parse!( - " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ - Domain=foo.com", - expected - ); - assert_eq_parse!( - " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ - Domain=FOO.COM", - expected - ); - - unexpected.set_path("/foo"); - unexpected.set_domain("bar.com"); - assert_ne_parse!( - " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ - Domain=foo.com", - unexpected - ); - assert_ne_parse!( - " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ - Domain=FOO.COM", - unexpected - ); - - let time_str = "Wed, 21 Oct 2015 07:28:00 GMT"; - let expires = PrimitiveDateTime::parse(time_str, "%a, %d %b %Y %H:%M:%S").unwrap().using_offset(offset!(UTC)); - expected.set_expires(expires); - assert_eq_parse!( - " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ - Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT", - expected - ); - - unexpected.set_domain("foo.com"); - let bad_expires = PrimitiveDateTime::parse(time_str, "%a, %d %b %Y %H:%S:%M").unwrap().using_offset(offset!(UTC)); - expected.set_expires(bad_expires); - assert_ne_parse!( - " foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \ - Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT", - unexpected - ); - } - - #[test] - fn odd_characters() { - let expected = Cookie::new("foo", "b%2Fr"); - assert_eq_parse!("foo=b%2Fr", expected); - } - - #[test] - fn odd_characters_encoded() { - let expected = Cookie::new("foo", "b/r"); - let cookie = match Cookie::parse_encoded("foo=b%2Fr") { - Ok(cookie) => cookie, - Err(e) => panic!("Failed to parse: {:?}", e), - }; - - assert_eq!(cookie, expected); - } - - #[test] - fn do_not_panic_on_large_max_ages() { - let max_duration = Duration::max_value(); - let expected = Cookie::build("foo", "bar").max_age_time(max_duration).finish(); - let overflow_duration = max_duration.checked_add(Duration::nanoseconds(1)).unwrap_or(max_duration); - assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", overflow_duration.whole_seconds()), expected); - } -} diff --git a/actix-http/src/cookie/secure/key.rs b/actix-http/src/cookie/secure/key.rs deleted file mode 100644 index 779c16b75..000000000 --- a/actix-http/src/cookie/secure/key.rs +++ /dev/null @@ -1,190 +0,0 @@ -use ring::hkdf::{Algorithm, KeyType, Prk, HKDF_SHA256}; -use ring::rand::{SecureRandom, SystemRandom}; - -use super::private::KEY_LEN as PRIVATE_KEY_LEN; -use super::signed::KEY_LEN as SIGNED_KEY_LEN; - -static HKDF_DIGEST: Algorithm = HKDF_SHA256; -const KEYS_INFO: &[&[u8]] = &[b"COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"]; - -/// A cryptographic master key for use with `Signed` and/or `Private` jars. -/// -/// This structure encapsulates secure, cryptographic keys for use with both -/// [PrivateJar](struct.PrivateJar.html) and [SignedJar](struct.SignedJar.html). -/// It can be derived from a single master key via -/// [from_master](#method.from_master) or generated from a secure random source -/// via [generate](#method.generate). A single instance of `Key` can be used for -/// both a `PrivateJar` and a `SignedJar`. -/// -/// This type is only available when the `secure` feature is enabled. -#[derive(Clone)] -pub struct Key { - signing_key: [u8; SIGNED_KEY_LEN], - encryption_key: [u8; PRIVATE_KEY_LEN], -} - -impl KeyType for &Key { - #[inline] - fn len(&self) -> usize { - SIGNED_KEY_LEN + PRIVATE_KEY_LEN - } -} - -impl Key { - /// Derives new signing/encryption keys from a master key. - /// - /// The master key must be at least 256-bits (32 bytes). For security, the - /// master key _must_ be cryptographically random. The keys are derived - /// deterministically from the master key. - /// - /// # Panics - /// - /// Panics if `key` is less than 32 bytes in length. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Key; - /// - /// # /* - /// let master_key = { /* a cryptographically random key >= 32 bytes */ }; - /// # */ - /// # let master_key: &Vec = &(0..32).collect(); - /// - /// let key = Key::from_master(master_key); - /// ``` - pub fn from_master(key: &[u8]) -> Key { - if key.len() < 32 { - panic!( - "bad master key length: expected at least 32 bytes, found {}", - key.len() - ); - } - - // An empty `Key` structure; will be filled in with HKDF derived keys. - let mut output_key = Key { - signing_key: [0; SIGNED_KEY_LEN], - encryption_key: [0; PRIVATE_KEY_LEN], - }; - - // Expand the master key into two HKDF generated keys. - let mut both_keys = [0; SIGNED_KEY_LEN + PRIVATE_KEY_LEN]; - let prk = Prk::new_less_safe(HKDF_DIGEST, key); - let okm = prk.expand(KEYS_INFO, &output_key).expect("okm expand"); - okm.fill(&mut both_keys).expect("fill keys"); - - // Copy the key parts into their respective fields. - output_key - .signing_key - .copy_from_slice(&both_keys[..SIGNED_KEY_LEN]); - output_key - .encryption_key - .copy_from_slice(&both_keys[SIGNED_KEY_LEN..]); - output_key - } - - /// Generates signing/encryption keys from a secure, random source. Keys are - /// generated nondeterministically. - /// - /// # Panics - /// - /// Panics if randomness cannot be retrieved from the operating system. See - /// [try_generate](#method.try_generate) for a non-panicking version. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Key; - /// - /// let key = Key::generate(); - /// ``` - pub fn generate() -> Key { - Self::try_generate().expect("failed to generate `Key` from randomness") - } - - /// Attempts to generate signing/encryption keys from a secure, random - /// source. Keys are generated nondeterministically. If randomness cannot be - /// retrieved from the underlying operating system, returns `None`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Key; - /// - /// let key = Key::try_generate(); - /// ``` - pub fn try_generate() -> Option { - let mut sign_key = [0; SIGNED_KEY_LEN]; - let mut enc_key = [0; PRIVATE_KEY_LEN]; - - let rng = SystemRandom::new(); - if rng.fill(&mut sign_key).is_err() || rng.fill(&mut enc_key).is_err() { - return None; - } - - Some(Key { - signing_key: sign_key, - encryption_key: enc_key, - }) - } - - /// Returns the raw bytes of a key suitable for signing cookies. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Key; - /// - /// let key = Key::generate(); - /// let signing_key = key.signing(); - /// ``` - pub fn signing(&self) -> &[u8] { - &self.signing_key[..] - } - - /// Returns the raw bytes of a key suitable for encrypting cookies. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::Key; - /// - /// let key = Key::generate(); - /// let encryption_key = key.encryption(); - /// ``` - pub fn encryption(&self) -> &[u8] { - &self.encryption_key[..] - } -} - -#[cfg(test)] -mod test { - use super::Key; - - #[test] - fn deterministic_from_master() { - let master_key: Vec = (0..32).collect(); - - let key_a = Key::from_master(&master_key); - let key_b = Key::from_master(&master_key); - - assert_eq!(key_a.signing(), key_b.signing()); - assert_eq!(key_a.encryption(), key_b.encryption()); - assert_ne!(key_a.encryption(), key_a.signing()); - - let master_key_2: Vec = (32..64).collect(); - let key_2 = Key::from_master(&master_key_2); - - assert_ne!(key_2.signing(), key_a.signing()); - assert_ne!(key_2.encryption(), key_a.encryption()); - } - - #[test] - fn non_deterministic_generate() { - let key_a = Key::generate(); - let key_b = Key::generate(); - - assert_ne!(key_a.signing(), key_b.signing()); - assert_ne!(key_a.encryption(), key_b.encryption()); - } -} diff --git a/actix-http/src/cookie/secure/macros.rs b/actix-http/src/cookie/secure/macros.rs deleted file mode 100644 index 089047c4e..000000000 --- a/actix-http/src/cookie/secure/macros.rs +++ /dev/null @@ -1,40 +0,0 @@ -#[cfg(test)] -macro_rules! assert_simple_behaviour { - ($clear:expr, $secure:expr) => {{ - assert_eq!($clear.iter().count(), 0); - - $secure.add(Cookie::new("name", "val")); - assert_eq!($clear.iter().count(), 1); - assert_eq!($secure.get("name").unwrap().value(), "val"); - assert_ne!($clear.get("name").unwrap().value(), "val"); - - $secure.add(Cookie::new("another", "two")); - assert_eq!($clear.iter().count(), 2); - - $clear.remove(Cookie::named("another")); - assert_eq!($clear.iter().count(), 1); - - $secure.remove(Cookie::named("name")); - assert_eq!($clear.iter().count(), 0); - }}; -} - -#[cfg(test)] -macro_rules! assert_secure_behaviour { - ($clear:expr, $secure:expr) => {{ - $secure.add(Cookie::new("secure", "secure")); - assert!($clear.get("secure").unwrap().value() != "secure"); - assert!($secure.get("secure").unwrap().value() == "secure"); - - let mut cookie = $clear.get("secure").unwrap().clone(); - let new_val = format!("{}l", cookie.value()); - cookie.set_value(new_val); - $clear.add(cookie); - assert!($secure.get("secure").is_none()); - - let mut cookie = $clear.get("secure").unwrap().clone(); - cookie.set_value("foobar"); - $clear.add(cookie); - assert!($secure.get("secure").is_none()); - }}; -} diff --git a/actix-http/src/cookie/secure/mod.rs b/actix-http/src/cookie/secure/mod.rs deleted file mode 100644 index e0fba9733..000000000 --- a/actix-http/src/cookie/secure/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Fork of https://github.com/alexcrichton/cookie-rs -#[macro_use] -mod macros; -mod key; -mod private; -mod signed; - -pub use self::key::*; -pub use self::private::*; -pub use self::signed::*; diff --git a/actix-http/src/cookie/secure/private.rs b/actix-http/src/cookie/secure/private.rs deleted file mode 100644 index f05e23800..000000000 --- a/actix-http/src/cookie/secure/private.rs +++ /dev/null @@ -1,275 +0,0 @@ -use std::str; - -use log::warn; -use ring::aead::{Aad, Algorithm, Nonce, AES_256_GCM}; -use ring::aead::{LessSafeKey, UnboundKey}; -use ring::rand::{SecureRandom, SystemRandom}; - -use super::Key; -use crate::cookie::{Cookie, CookieJar}; - -// Keep these in sync, and keep the key len synced with the `private` docs as -// well as the `KEYS_INFO` const in secure::Key. -static ALGO: &Algorithm = &AES_256_GCM; -const NONCE_LEN: usize = 12; -pub const KEY_LEN: usize = 32; - -/// A child cookie jar that provides authenticated encryption for its cookies. -/// -/// A _private_ child jar signs and encrypts all the cookies added to it and -/// verifies and decrypts cookies retrieved from it. Any cookies stored in a -/// `PrivateJar` are simultaneously assured confidentiality, integrity, and -/// authenticity. In other words, clients cannot discover nor tamper with the -/// contents of a cookie, nor can they fabricate cookie data. -/// -/// This type is only available when the `secure` feature is enabled. -pub struct PrivateJar<'a> { - parent: &'a mut CookieJar, - key: [u8; KEY_LEN], -} - -impl<'a> PrivateJar<'a> { - /// Creates a new child `PrivateJar` with parent `parent` and key `key`. - /// This method is typically called indirectly via the `signed` method of - /// `CookieJar`. - #[doc(hidden)] - pub fn new(parent: &'a mut CookieJar, key: &Key) -> PrivateJar<'a> { - let mut key_array = [0u8; KEY_LEN]; - key_array.copy_from_slice(key.encryption()); - PrivateJar { - parent, - key: key_array, - } - } - - /// Given a sealed value `str` and a key name `name`, where the nonce is - /// prepended to the original value and then both are Base64 encoded, - /// verifies and decrypts the sealed value and returns it. If there's a - /// problem, returns an `Err` with a string describing the issue. - fn unseal(&self, name: &str, value: &str) -> Result { - let mut data = base64::decode(value).map_err(|_| "bad base64 value")?; - if data.len() <= NONCE_LEN { - return Err("length of decoded data is <= NONCE_LEN"); - } - - let ad = Aad::from(name.as_bytes()); - let key = LessSafeKey::new( - UnboundKey::new(&ALGO, &self.key).expect("matching key length"), - ); - let (nonce, mut sealed) = data.split_at_mut(NONCE_LEN); - let nonce = - Nonce::try_assume_unique_for_key(nonce).expect("invalid length of `nonce`"); - let unsealed = key - .open_in_place(nonce, ad, &mut sealed) - .map_err(|_| "invalid key/nonce/value: bad seal")?; - - if let Ok(unsealed_utf8) = str::from_utf8(unsealed) { - Ok(unsealed_utf8.to_string()) - } else { - warn!( - "Private cookie does not have utf8 content! -It is likely the secret key used to encrypt them has been leaked. -Please change it as soon as possible." - ); - Err("bad unsealed utf8") - } - } - - /// Returns a reference to the `Cookie` inside this jar with the name `name` - /// and authenticates and decrypts the cookie's value, returning a `Cookie` - /// with the decrypted value. If the cookie cannot be found, or the cookie - /// fails to authenticate or decrypt, `None` is returned. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie, Key}; - /// - /// let key = Key::generate(); - /// let mut jar = CookieJar::new(); - /// let mut private_jar = jar.private(&key); - /// assert!(private_jar.get("name").is_none()); - /// - /// private_jar.add(Cookie::new("name", "value")); - /// assert_eq!(private_jar.get("name").unwrap().value(), "value"); - /// ``` - pub fn get(&self, name: &str) -> Option> { - if let Some(cookie_ref) = self.parent.get(name) { - let mut cookie = cookie_ref.clone(); - if let Ok(value) = self.unseal(name, cookie.value()) { - cookie.set_value(value); - return Some(cookie); - } - } - - None - } - - /// Adds `cookie` to the parent jar. The cookie's value is encrypted with - /// authenticated encryption assuring confidentiality, integrity, and - /// authenticity. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie, Key}; - /// - /// let key = Key::generate(); - /// let mut jar = CookieJar::new(); - /// jar.private(&key).add(Cookie::new("name", "value")); - /// - /// assert_ne!(jar.get("name").unwrap().value(), "value"); - /// assert_eq!(jar.private(&key).get("name").unwrap().value(), "value"); - /// ``` - pub fn add(&mut self, mut cookie: Cookie<'static>) { - self.encrypt_cookie(&mut cookie); - - // Add the sealed cookie to the parent. - self.parent.add(cookie); - } - - /// Adds an "original" `cookie` to parent jar. The cookie's value is - /// encrypted with authenticated encryption assuring confidentiality, - /// integrity, and authenticity. Adding an original cookie does not affect - /// the [`CookieJar::delta()`](struct.CookieJar.html#method.delta) - /// computation. This method is intended to be used to seed the cookie jar - /// with cookies received from a client's HTTP message. - /// - /// For accurate `delta` computations, this method should not be called - /// after calling `remove`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie, Key}; - /// - /// let key = Key::generate(); - /// let mut jar = CookieJar::new(); - /// jar.private(&key).add_original(Cookie::new("name", "value")); - /// - /// assert_eq!(jar.iter().count(), 1); - /// assert_eq!(jar.delta().count(), 0); - /// ``` - pub fn add_original(&mut self, mut cookie: Cookie<'static>) { - self.encrypt_cookie(&mut cookie); - - // Add the sealed cookie to the parent. - self.parent.add_original(cookie); - } - - /// Encrypts the cookie's value with - /// authenticated encryption assuring confidentiality, integrity, and authenticity. - fn encrypt_cookie(&self, cookie: &mut Cookie<'_>) { - let name = cookie.name().as_bytes(); - let value = cookie.value().as_bytes(); - let data = encrypt_name_value(name, value, &self.key); - - // Base64 encode the nonce and encrypted value. - let sealed_value = base64::encode(&data); - cookie.set_value(sealed_value); - } - - /// Removes `cookie` from the parent jar. - /// - /// For correct removal, the passed in `cookie` must contain the same `path` - /// and `domain` as the cookie that was initially set. - /// - /// See [CookieJar::remove](struct.CookieJar.html#method.remove) for more - /// details. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie, Key}; - /// - /// let key = Key::generate(); - /// let mut jar = CookieJar::new(); - /// let mut private_jar = jar.private(&key); - /// - /// private_jar.add(Cookie::new("name", "value")); - /// assert!(private_jar.get("name").is_some()); - /// - /// private_jar.remove(Cookie::named("name")); - /// assert!(private_jar.get("name").is_none()); - /// ``` - pub fn remove(&mut self, cookie: Cookie<'static>) { - self.parent.remove(cookie); - } -} - -fn encrypt_name_value(name: &[u8], value: &[u8], key: &[u8]) -> Vec { - // Create the `SealingKey` structure. - let unbound = UnboundKey::new(&ALGO, key).expect("matching key length"); - let key = LessSafeKey::new(unbound); - - // Create a vec to hold the [nonce | cookie value | overhead]. - let mut data = vec![0; NONCE_LEN + value.len() + ALGO.tag_len()]; - - // Randomly generate the nonce, then copy the cookie value as input. - let (nonce, in_out) = data.split_at_mut(NONCE_LEN); - let (in_out, tag) = in_out.split_at_mut(value.len()); - in_out.copy_from_slice(value); - - // Randomly generate the nonce into the nonce piece. - SystemRandom::new() - .fill(nonce) - .expect("couldn't random fill nonce"); - let nonce = Nonce::try_assume_unique_for_key(nonce).expect("invalid `nonce` length"); - - // Use cookie's name as associated data to prevent value swapping. - let ad = Aad::from(name); - let ad_tag = key - .seal_in_place_separate_tag(nonce, ad, in_out) - .expect("in-place seal"); - - // Copy the tag into the tag piece. - tag.copy_from_slice(ad_tag.as_ref()); - - // Remove the overhead and return the sealed content. - data -} - -#[cfg(test)] -mod test { - use super::{encrypt_name_value, Cookie, CookieJar, Key}; - - #[test] - fn simple() { - let key = Key::generate(); - let mut jar = CookieJar::new(); - assert_simple_behaviour!(jar, jar.private(&key)); - } - - #[test] - fn private() { - let key = Key::generate(); - let mut jar = CookieJar::new(); - assert_secure_behaviour!(jar, jar.private(&key)); - } - - #[test] - fn non_utf8() { - let key = Key::generate(); - let mut jar = CookieJar::new(); - - let name = "malicious"; - let mut assert_non_utf8 = |value: &[u8]| { - let sealed = encrypt_name_value(name.as_bytes(), value, &key.encryption()); - let encoded = base64::encode(&sealed); - assert_eq!( - jar.private(&key).unseal(name, &encoded), - Err("bad unsealed utf8") - ); - jar.add(Cookie::new(name, encoded)); - assert_eq!(jar.private(&key).get(name), None); - }; - - assert_non_utf8(&[0x72, 0xfb, 0xdf, 0x74]); // rûst in ISO/IEC 8859-1 - - let mut malicious = - String::from(r#"{"id":"abc123??%X","admin":true}"#).into_bytes(); - malicious[8] |= 0b1100_0000; - malicious[9] |= 0b1100_0000; - assert_non_utf8(&malicious); - } -} diff --git a/actix-http/src/cookie/secure/signed.rs b/actix-http/src/cookie/secure/signed.rs deleted file mode 100644 index 64e8d5dda..000000000 --- a/actix-http/src/cookie/secure/signed.rs +++ /dev/null @@ -1,184 +0,0 @@ -use ring::hmac::{self, sign, verify}; - -use super::Key; -use crate::cookie::{Cookie, CookieJar}; - -// Keep these in sync, and keep the key len synced with the `signed` docs as -// well as the `KEYS_INFO` const in secure::Key. -static HMAC_DIGEST: hmac::Algorithm = hmac::HMAC_SHA256; -const BASE64_DIGEST_LEN: usize = 44; -pub const KEY_LEN: usize = 32; - -/// A child cookie jar that authenticates its cookies. -/// -/// A _signed_ child jar signs all the cookies added to it and verifies cookies -/// retrieved from it. Any cookies stored in a `SignedJar` are assured integrity -/// and authenticity. In other words, clients cannot tamper with the contents of -/// a cookie nor can they fabricate cookie values, but the data is visible in -/// plaintext. -/// -/// This type is only available when the `secure` feature is enabled. -pub struct SignedJar<'a> { - parent: &'a mut CookieJar, - key: hmac::Key, -} - -impl<'a> SignedJar<'a> { - /// Creates a new child `SignedJar` with parent `parent` and key `key`. This - /// method is typically called indirectly via the `signed` method of - /// `CookieJar`. - #[doc(hidden)] - pub fn new(parent: &'a mut CookieJar, key: &Key) -> SignedJar<'a> { - SignedJar { - parent, - key: hmac::Key::new(HMAC_DIGEST, key.signing()), - } - } - - /// Given a signed value `str` where the signature is prepended to `value`, - /// verifies the signed value and returns it. If there's a problem, returns - /// an `Err` with a string describing the issue. - fn verify(&self, cookie_value: &str) -> Result { - if cookie_value.len() < BASE64_DIGEST_LEN { - return Err("length of value is <= BASE64_DIGEST_LEN"); - } - - let (digest_str, value) = cookie_value.split_at(BASE64_DIGEST_LEN); - let sig = base64::decode(digest_str).map_err(|_| "bad base64 digest")?; - - verify(&self.key, value.as_bytes(), &sig) - .map(|_| value.to_string()) - .map_err(|_| "value did not verify") - } - - /// Returns a reference to the `Cookie` inside this jar with the name `name` - /// and verifies the authenticity and integrity of the cookie's value, - /// returning a `Cookie` with the authenticated value. If the cookie cannot - /// be found, or the cookie fails to verify, `None` is returned. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie, Key}; - /// - /// let key = Key::generate(); - /// let mut jar = CookieJar::new(); - /// let mut signed_jar = jar.signed(&key); - /// assert!(signed_jar.get("name").is_none()); - /// - /// signed_jar.add(Cookie::new("name", "value")); - /// assert_eq!(signed_jar.get("name").unwrap().value(), "value"); - /// ``` - pub fn get(&self, name: &str) -> Option> { - if let Some(cookie_ref) = self.parent.get(name) { - let mut cookie = cookie_ref.clone(); - if let Ok(value) = self.verify(cookie.value()) { - cookie.set_value(value); - return Some(cookie); - } - } - - None - } - - /// Adds `cookie` to the parent jar. The cookie's value is signed assuring - /// integrity and authenticity. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie, Key}; - /// - /// let key = Key::generate(); - /// let mut jar = CookieJar::new(); - /// jar.signed(&key).add(Cookie::new("name", "value")); - /// - /// assert_ne!(jar.get("name").unwrap().value(), "value"); - /// assert!(jar.get("name").unwrap().value().contains("value")); - /// assert_eq!(jar.signed(&key).get("name").unwrap().value(), "value"); - /// ``` - pub fn add(&mut self, mut cookie: Cookie<'static>) { - self.sign_cookie(&mut cookie); - self.parent.add(cookie); - } - - /// Adds an "original" `cookie` to this jar. The cookie's value is signed - /// assuring integrity and authenticity. Adding an original cookie does not - /// affect the [`CookieJar::delta()`](struct.CookieJar.html#method.delta) - /// computation. This method is intended to be used to seed the cookie jar - /// with cookies received from a client's HTTP message. - /// - /// For accurate `delta` computations, this method should not be called - /// after calling `remove`. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie, Key}; - /// - /// let key = Key::generate(); - /// let mut jar = CookieJar::new(); - /// jar.signed(&key).add_original(Cookie::new("name", "value")); - /// - /// assert_eq!(jar.iter().count(), 1); - /// assert_eq!(jar.delta().count(), 0); - /// ``` - pub fn add_original(&mut self, mut cookie: Cookie<'static>) { - self.sign_cookie(&mut cookie); - self.parent.add_original(cookie); - } - - /// Signs the cookie's value assuring integrity and authenticity. - fn sign_cookie(&self, cookie: &mut Cookie<'_>) { - let digest = sign(&self.key, cookie.value().as_bytes()); - let mut new_value = base64::encode(digest.as_ref()); - new_value.push_str(cookie.value()); - cookie.set_value(new_value); - } - - /// Removes `cookie` from the parent jar. - /// - /// For correct removal, the passed in `cookie` must contain the same `path` - /// and `domain` as the cookie that was initially set. - /// - /// See [CookieJar::remove](struct.CookieJar.html#method.remove) for more - /// details. - /// - /// # Example - /// - /// ```rust - /// use actix_http::cookie::{CookieJar, Cookie, Key}; - /// - /// let key = Key::generate(); - /// let mut jar = CookieJar::new(); - /// let mut signed_jar = jar.signed(&key); - /// - /// signed_jar.add(Cookie::new("name", "value")); - /// assert!(signed_jar.get("name").is_some()); - /// - /// signed_jar.remove(Cookie::named("name")); - /// assert!(signed_jar.get("name").is_none()); - /// ``` - pub fn remove(&mut self, cookie: Cookie<'static>) { - self.parent.remove(cookie); - } -} - -#[cfg(test)] -mod test { - use super::{Cookie, CookieJar, Key}; - - #[test] - fn simple() { - let key = Key::generate(); - let mut jar = CookieJar::new(); - assert_simple_behaviour!(jar, jar.signed(&key)); - } - - #[test] - fn private() { - let key = Key::generate(); - let mut jar = CookieJar::new(); - assert_secure_behaviour!(jar, jar.signed(&key)); - } -} diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs deleted file mode 100644 index b60435859..000000000 --- a/actix-http/src/encoding/decoder.rs +++ /dev/null @@ -1,222 +0,0 @@ -use std::future::Future; -use std::io::{self, Write}; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix_threadpool::{run, CpuFuture}; -use brotli2::write::BrotliDecoder; -use bytes::Bytes; -use flate2::write::{GzDecoder, ZlibDecoder}; -use futures_core::{ready, Stream}; - -use super::Writer; -use crate::error::PayloadError; -use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}; - -const INPLACE: usize = 2049; - -pub struct Decoder { - decoder: Option, - stream: S, - eof: bool, - fut: Option, ContentDecoder), io::Error>>, -} - -impl Decoder -where - S: Stream>, -{ - /// Construct a decoder. - #[inline] - pub fn new(stream: S, encoding: ContentEncoding) -> Decoder { - let decoder = match encoding { - ContentEncoding::Br => Some(ContentDecoder::Br(Box::new( - BrotliDecoder::new(Writer::new()), - ))), - ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new( - ZlibDecoder::new(Writer::new()), - ))), - ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new( - GzDecoder::new(Writer::new()), - ))), - _ => None, - }; - Decoder { - decoder, - stream, - fut: None, - eof: false, - } - } - - /// Construct decoder based on headers. - #[inline] - pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder { - // check content-encoding - let encoding = if let Some(enc) = headers.get(&CONTENT_ENCODING) { - if let Ok(enc) = enc.to_str() { - ContentEncoding::from(enc) - } else { - ContentEncoding::Identity - } - } else { - ContentEncoding::Identity - }; - - Self::new(stream, encoding) - } -} - -impl Stream for Decoder -where - S: Stream> + Unpin, -{ - type Item = Result; - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - loop { - if let Some(ref mut fut) = self.fut { - let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) { - Ok(item) => item, - Err(e) => return Poll::Ready(Some(Err(e.into()))), - }; - self.decoder = Some(decoder); - self.fut.take(); - if let Some(chunk) = chunk { - return Poll::Ready(Some(Ok(chunk))); - } - } - - if self.eof { - return Poll::Ready(None); - } - - match Pin::new(&mut self.stream).poll_next(cx) { - Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))), - Poll::Ready(Some(Ok(chunk))) => { - if let Some(mut decoder) = self.decoder.take() { - if chunk.len() < INPLACE { - let chunk = decoder.feed_data(chunk)?; - self.decoder = Some(decoder); - if let Some(chunk) = chunk { - return Poll::Ready(Some(Ok(chunk))); - } - } else { - self.fut = Some(run(move || { - let chunk = decoder.feed_data(chunk)?; - Ok((chunk, decoder)) - })); - } - continue; - } else { - return Poll::Ready(Some(Ok(chunk))); - } - } - Poll::Ready(None) => { - self.eof = true; - return if let Some(mut decoder) = self.decoder.take() { - match decoder.feed_eof() { - Ok(Some(res)) => Poll::Ready(Some(Ok(res))), - Ok(None) => Poll::Ready(None), - Err(err) => Poll::Ready(Some(Err(err.into()))), - } - } else { - Poll::Ready(None) - }; - } - Poll::Pending => break, - } - } - Poll::Pending - } -} - -enum ContentDecoder { - Deflate(Box>), - Gzip(Box>), - Br(Box>), -} - -impl ContentDecoder { - fn feed_eof(&mut self) -> io::Result> { - match self { - ContentDecoder::Br(ref mut decoder) => match decoder.flush() { - Ok(()) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - } - } - - fn feed_data(&mut self, data: Bytes) -> io::Result> { - match self { - ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - } - } -} diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs deleted file mode 100644 index ca04845ab..000000000 --- a/actix-http/src/encoding/encoder.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! Stream encoder -use std::future::Future; -use std::io::{self, Write}; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix_threadpool::{run, CpuFuture}; -use brotli2::write::BrotliEncoder; -use bytes::Bytes; -use flate2::write::{GzEncoder, ZlibEncoder}; -use futures_core::ready; - -use crate::body::{Body, BodySize, MessageBody, ResponseBody}; -use crate::http::header::{ContentEncoding, CONTENT_ENCODING}; -use crate::http::{HeaderValue, StatusCode}; -use crate::{Error, ResponseHead}; - -use super::Writer; - -const INPLACE: usize = 1024; - -pub struct Encoder { - eof: bool, - body: EncoderBody, - encoder: Option, - fut: Option>, -} - -impl Encoder { - pub fn response( - encoding: ContentEncoding, - head: &mut ResponseHead, - body: ResponseBody, - ) -> ResponseBody> { - let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING) - || head.status == StatusCode::SWITCHING_PROTOCOLS - || head.status == StatusCode::NO_CONTENT - || encoding == ContentEncoding::Identity - || encoding == ContentEncoding::Auto); - - let body = match body { - ResponseBody::Other(b) => match b { - Body::None => return ResponseBody::Other(Body::None), - Body::Empty => return ResponseBody::Other(Body::Empty), - Body::Bytes(buf) => { - if can_encode { - EncoderBody::Bytes(buf) - } else { - return ResponseBody::Other(Body::Bytes(buf)); - } - } - Body::Message(stream) => EncoderBody::BoxedStream(stream), - }, - ResponseBody::Body(stream) => EncoderBody::Stream(stream), - }; - - if can_encode { - // Modify response body only if encoder is not None - if let Some(enc) = ContentEncoder::encoder(encoding) { - update_head(encoding, head); - head.no_chunking(false); - return ResponseBody::Body(Encoder { - body, - eof: false, - fut: None, - encoder: Some(enc), - }); - } - } - ResponseBody::Body(Encoder { - body, - eof: false, - fut: None, - encoder: None, - }) - } -} - -enum EncoderBody { - Bytes(Bytes), - Stream(B), - BoxedStream(Box), -} - -impl MessageBody for Encoder { - fn size(&self) -> BodySize { - if self.encoder.is_none() { - match self.body { - EncoderBody::Bytes(ref b) => b.size(), - EncoderBody::Stream(ref b) => b.size(), - EncoderBody::BoxedStream(ref b) => b.size(), - } - } else { - BodySize::Stream - } - } - - fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll>> { - loop { - if self.eof { - return Poll::Ready(None); - } - - if let Some(ref mut fut) = self.fut { - let mut encoder = match ready!(Pin::new(fut).poll(cx)) { - Ok(item) => item, - Err(e) => return Poll::Ready(Some(Err(e.into()))), - }; - let chunk = encoder.take(); - self.encoder = Some(encoder); - self.fut.take(); - if !chunk.is_empty() { - return Poll::Ready(Some(Ok(chunk))); - } - } - - let result = match self.body { - EncoderBody::Bytes(ref mut b) => { - if b.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(std::mem::replace(b, Bytes::new())))) - } - } - EncoderBody::Stream(ref mut b) => b.poll_next(cx), - EncoderBody::BoxedStream(ref mut b) => b.poll_next(cx), - }; - match result { - Poll::Ready(Some(Ok(chunk))) => { - if let Some(mut encoder) = self.encoder.take() { - if chunk.len() < INPLACE { - encoder.write(&chunk)?; - let chunk = encoder.take(); - self.encoder = Some(encoder); - if !chunk.is_empty() { - return Poll::Ready(Some(Ok(chunk))); - } - } else { - self.fut = Some(run(move || { - encoder.write(&chunk)?; - Ok(encoder) - })); - } - } else { - return Poll::Ready(Some(Ok(chunk))); - } - } - Poll::Ready(None) => { - if let Some(encoder) = self.encoder.take() { - let chunk = encoder.finish()?; - if chunk.is_empty() { - return Poll::Ready(None); - } else { - self.eof = true; - return Poll::Ready(Some(Ok(chunk))); - } - } else { - return Poll::Ready(None); - } - } - val => return val, - } - } - } -} - -fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { - head.headers_mut().insert( - CONTENT_ENCODING, - HeaderValue::from_static(encoding.as_str()), - ); -} - -enum ContentEncoder { - Deflate(ZlibEncoder), - Gzip(GzEncoder), - Br(BrotliEncoder), -} - -impl ContentEncoder { - fn encoder(encoding: ContentEncoding) -> Option { - match encoding { - ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new( - Writer::new(), - flate2::Compression::fast(), - ))), - ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new( - Writer::new(), - flate2::Compression::fast(), - ))), - ContentEncoding::Br => { - Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3))) - } - _ => None, - } - } - - #[inline] - pub(crate) fn take(&mut self) -> Bytes { - match *self { - ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(), - ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(), - ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(), - } - } - - fn finish(self) -> Result { - match self { - ContentEncoder::Br(encoder) => match encoder.finish() { - Ok(writer) => Ok(writer.buf.freeze()), - Err(err) => Err(err), - }, - ContentEncoder::Gzip(encoder) => match encoder.finish() { - Ok(writer) => Ok(writer.buf.freeze()), - Err(err) => Err(err), - }, - ContentEncoder::Deflate(encoder) => match encoder.finish() { - Ok(writer) => Ok(writer.buf.freeze()), - Err(err) => Err(err), - }, - } - } - - fn write(&mut self, data: &[u8]) -> Result<(), io::Error> { - match *self { - ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding br encoding: {}", err); - Err(err) - } - }, - ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding gzip encoding: {}", err); - Err(err) - } - }, - ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { - Ok(_) => Ok(()), - Err(err) => { - trace!("Error decoding deflate encoding: {}", err); - Err(err) - } - }, - } - } -} diff --git a/actix-http/src/encoding/mod.rs b/actix-http/src/encoding/mod.rs deleted file mode 100644 index 9eaf4104e..000000000 --- a/actix-http/src/encoding/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! Content-Encoding support -use std::io; - -use bytes::{Bytes, BytesMut}; - -mod decoder; -mod encoder; - -pub use self::decoder::Decoder; -pub use self::encoder::Encoder; - -pub(self) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::with_capacity(8192), - } - } - - fn take(&mut self) -> Bytes { - self.buf.split().freeze() - } -} - -impl io::Write for Writer { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.extend_from_slice(buf); - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs deleted file mode 100644 index fd0fe927f..000000000 --- a/actix-http/src/error.rs +++ /dev/null @@ -1,1210 +0,0 @@ -//! Error and Result module -use std::any::TypeId; -use std::cell::RefCell; -use std::io::Write; -use std::str::Utf8Error; -use std::string::FromUtf8Error; -use std::{fmt, io, result}; - -use actix_codec::{Decoder, Encoder}; -pub use actix_threadpool::BlockingError; -use actix_utils::framed::DispatcherError as FramedDispatcherError; -use actix_utils::timeout::TimeoutError; -use bytes::BytesMut; -use derive_more::{Display, From}; -pub use futures_channel::oneshot::Canceled; -use http::uri::InvalidUri; -use http::{header, Error as HttpError, StatusCode}; -use httparse; -use serde::de::value::Error as DeError; -use serde_json::error::Error as JsonError; -use serde_urlencoded::ser::Error as FormError; - -// re-export for convinience -use crate::body::Body; -pub use crate::cookie::ParseError as CookieParseError; -use crate::helpers::Writer; -use crate::response::{Response, ResponseBuilder}; - -/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) -/// for actix web operations -/// -/// This typedef is generally used to avoid writing out -/// `actix_http::error::Error` directly and is otherwise a direct mapping to -/// `Result`. -pub type Result = result::Result; - -/// General purpose actix web error. -/// -/// An actix web error is used to carry errors from `failure` or `std::error` -/// through actix in a convenient way. It can be created through -/// converting errors with `into()`. -/// -/// Whenever it is created from an external object a response error is created -/// for it that can be used to create an http response from it this means that -/// if you have access to an actix `Error` you can always get a -/// `ResponseError` reference from it. -pub struct Error { - cause: Box, -} - -impl Error { - /// Returns the reference to the underlying `ResponseError`. - pub fn as_response_error(&self) -> &dyn ResponseError { - self.cause.as_ref() - } - - /// Similar to `as_response_error` but downcasts. - pub fn as_error(&self) -> Option<&T> { - ResponseError::downcast_ref(self.cause.as_ref()) - } -} - -/// Error that can be converted to `Response` -pub trait ResponseError: fmt::Debug + fmt::Display { - /// Response's status code - /// - /// Internal server error is generated by default. - fn status_code(&self) -> StatusCode { - StatusCode::INTERNAL_SERVER_ERROR - } - - /// Create response for error - /// - /// Internal server error is generated by default. - fn error_response(&self) -> Response { - let mut resp = Response::new(self.status_code()); - let mut buf = BytesMut::new(); - let _ = write!(Writer(&mut buf), "{}", self); - resp.headers_mut().insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain; charset=utf-8"), - ); - resp.set_body(Body::from(buf)) - } - - #[doc(hidden)] - fn __private_get_type_id__(&self) -> TypeId - where - Self: 'static, - { - TypeId::of::() - } -} - -impl dyn ResponseError + 'static { - /// Downcasts a response error to a specific type. - pub fn downcast_ref(&self) -> Option<&T> { - if self.__private_get_type_id__() == TypeId::of::() { - unsafe { Some(&*(self as *const dyn ResponseError as *const T)) } - } else { - None - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.cause, f) - } -} - -impl fmt::Debug for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", &self.cause) - } -} - -impl std::error::Error for Error { - fn cause(&self) -> Option<&dyn std::error::Error> { - None - } - - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None - } -} - -impl From<()> for Error { - fn from(_: ()) -> Self { - Error::from(UnitError) - } -} - -impl From for Error { - fn from(_: std::convert::Infallible) -> Self { - // `std::convert::Infallible` indicates an error - // that will never happen - unreachable!() - } -} - -/// Convert `Error` to a `Response` instance -impl From for Response { - fn from(err: Error) -> Self { - Response::from_error(err) - } -} - -/// `Error` for any error that implements `ResponseError` -impl From for Error { - fn from(err: T) -> Error { - Error { - cause: Box::new(err), - } - } -} - -/// Convert Response to a Error -impl From for Error { - fn from(res: Response) -> Error { - InternalError::from_response("", res).into() - } -} - -/// Convert ResponseBuilder to a Error -impl From for Error { - fn from(mut res: ResponseBuilder) -> Error { - InternalError::from_response("", res.finish()).into() - } -} - -/// Return `GATEWAY_TIMEOUT` for `TimeoutError` -impl ResponseError for TimeoutError { - fn status_code(&self) -> StatusCode { - match self { - TimeoutError::Service(e) => e.status_code(), - TimeoutError::Timeout => StatusCode::GATEWAY_TIMEOUT, - } - } -} - -#[derive(Debug, Display)] -#[display(fmt = "UnknownError")] -struct UnitError; - -/// `InternalServerError` for `UnitError` -impl ResponseError for UnitError {} - -/// `InternalServerError` for `JsonError` -impl ResponseError for JsonError {} - -/// `InternalServerError` for `FormError` -impl ResponseError for FormError {} - -#[cfg(feature = "openssl")] -/// `InternalServerError` for `openssl::ssl::Error` -impl ResponseError for actix_connect::ssl::openssl::SslError {} - -#[cfg(feature = "openssl")] -/// `InternalServerError` for `openssl::ssl::HandshakeError` -impl ResponseError for actix_tls::openssl::HandshakeError {} - -/// Return `BAD_REQUEST` for `de::value::Error` -impl ResponseError for DeError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - -/// `InternalServerError` for `Canceled` -impl ResponseError for Canceled {} - -/// `InternalServerError` for `BlockingError` -impl ResponseError for BlockingError {} - -/// Return `BAD_REQUEST` for `Utf8Error` -impl ResponseError for Utf8Error { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - -/// Return `InternalServerError` for `HttpError`, -/// Response generation can return `HttpError`, so it is internal error -impl ResponseError for HttpError {} - -/// Return `InternalServerError` for `io::Error` -impl ResponseError for io::Error { - fn status_code(&self) -> StatusCode { - match self.kind() { - io::ErrorKind::NotFound => StatusCode::NOT_FOUND, - io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN, - _ => StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - -/// `BadRequest` for `InvalidHeaderValue` -impl ResponseError for header::InvalidHeaderValue { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - -/// A set of errors that can occur during parsing HTTP streams -#[derive(Debug, Display)] -pub enum ParseError { - /// An invalid `Method`, such as `GE.T`. - #[display(fmt = "Invalid Method specified")] - Method, - /// An invalid `Uri`, such as `exam ple.domain`. - #[display(fmt = "Uri error: {}", _0)] - Uri(InvalidUri), - /// An invalid `HttpVersion`, such as `HTP/1.1` - #[display(fmt = "Invalid HTTP version specified")] - Version, - /// An invalid `Header`. - #[display(fmt = "Invalid Header provided")] - Header, - /// A message head is too large to be reasonable. - #[display(fmt = "Message head is too large")] - TooLarge, - /// A message reached EOF, but is not complete. - #[display(fmt = "Message is incomplete")] - Incomplete, - /// An invalid `Status`, such as `1337 ELITE`. - #[display(fmt = "Invalid Status provided")] - Status, - /// A timeout occurred waiting for an IO event. - #[allow(dead_code)] - #[display(fmt = "Timeout")] - Timeout, - /// An `io::Error` that occurred while trying to read or write to a network - /// stream. - #[display(fmt = "IO error: {}", _0)] - Io(io::Error), - /// Parsing a field as string failed - #[display(fmt = "UTF8 error: {}", _0)] - Utf8(Utf8Error), -} - -/// Return `BadRequest` for `ParseError` -impl ResponseError for ParseError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - -impl From for ParseError { - fn from(err: io::Error) -> ParseError { - ParseError::Io(err) - } -} - -impl From for ParseError { - fn from(err: InvalidUri) -> ParseError { - ParseError::Uri(err) - } -} - -impl From for ParseError { - fn from(err: Utf8Error) -> ParseError { - ParseError::Utf8(err) - } -} - -impl From for ParseError { - fn from(err: FromUtf8Error) -> ParseError { - ParseError::Utf8(err.utf8_error()) - } -} - -impl From for ParseError { - fn from(err: httparse::Error) -> ParseError { - match err { - httparse::Error::HeaderName - | httparse::Error::HeaderValue - | httparse::Error::NewLine - | httparse::Error::Token => ParseError::Header, - httparse::Error::Status => ParseError::Status, - httparse::Error::TooManyHeaders => ParseError::TooLarge, - httparse::Error::Version => ParseError::Version, - } - } -} - -#[derive(Display, Debug)] -/// A set of errors that can occur during payload parsing -pub enum PayloadError { - /// A payload reached EOF, but is not complete. - #[display( - fmt = "A payload reached EOF, but is not complete. With error: {:?}", - _0 - )] - Incomplete(Option), - /// Content encoding stream corruption - #[display(fmt = "Can not decode content-encoding.")] - EncodingCorrupted, - /// A payload reached size limit. - #[display(fmt = "A payload reached size limit.")] - Overflow, - /// A payload length is unknown. - #[display(fmt = "A payload length is unknown.")] - UnknownLength, - /// Http2 payload error - #[display(fmt = "{}", _0)] - Http2Payload(h2::Error), - /// Io error - #[display(fmt = "{}", _0)] - Io(io::Error), -} - -impl From for PayloadError { - fn from(err: h2::Error) -> Self { - PayloadError::Http2Payload(err) - } -} - -impl From> for PayloadError { - fn from(err: Option) -> Self { - PayloadError::Incomplete(err) - } -} - -impl From for PayloadError { - fn from(err: io::Error) -> Self { - PayloadError::Incomplete(Some(err)) - } -} - -impl From> for PayloadError { - fn from(err: BlockingError) -> Self { - match err { - BlockingError::Error(e) => PayloadError::Io(e), - BlockingError::Canceled => PayloadError::Io(io::Error::new( - io::ErrorKind::Other, - "Operation is canceled", - )), - } - } -} - -/// `PayloadError` returns two possible results: -/// -/// - `Overflow` returns `PayloadTooLarge` -/// - Other errors returns `BadRequest` -impl ResponseError for PayloadError { - fn status_code(&self) -> StatusCode { - match *self { - PayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE, - _ => StatusCode::BAD_REQUEST, - } - } -} - -/// Return `BadRequest` for `cookie::ParseError` -impl ResponseError for crate::cookie::ParseError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - -#[derive(Debug, Display, From)] -/// A set of errors that can occur during dispatching http requests -pub enum DispatchError { - /// Service error - Service(Error), - - /// Upgrade service error - Upgrade, - - /// An `io::Error` that occurred while trying to read or write to a network - /// stream. - #[display(fmt = "IO error: {}", _0)] - Io(io::Error), - - /// Http request parse error. - #[display(fmt = "Parse error: {}", _0)] - Parse(ParseError), - - /// Http/2 error - #[display(fmt = "{}", _0)] - H2(h2::Error), - - /// The first request did not complete within the specified timeout. - #[display(fmt = "The first request did not complete within the specified timeout")] - SlowRequestTimeout, - - /// Disconnect timeout. Makes sense for ssl streams. - #[display(fmt = "Connection shutdown timeout")] - DisconnectTimeout, - - /// Payload is not consumed - #[display(fmt = "Task is completed but request's payload is not consumed")] - PayloadIsNotConsumed, - - /// Malformed request - #[display(fmt = "Malformed request")] - MalformedRequest, - - /// Internal error - #[display(fmt = "Internal error")] - InternalError, - - /// Unknown error - #[display(fmt = "Unknown error")] - Unknown, -} - -/// A set of error that can occure during parsing content type -#[derive(PartialEq, Debug, Display)] -pub enum ContentTypeError { - /// Can not parse content type - #[display(fmt = "Can not parse content type")] - ParseError, - /// Unknown content encoding - #[display(fmt = "Unknown content encoding")] - UnknownEncoding, -} - -/// Return `BadRequest` for `ContentTypeError` -impl ResponseError for ContentTypeError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - -impl ResponseError for FramedDispatcherError -where - E: fmt::Debug + fmt::Display, - ::Error: fmt::Debug, - ::Error: fmt::Debug, -{ -} - -/// Helper type that can wrap any error and generate custom response. -/// -/// In following example any `io::Error` will be converted into "BAD REQUEST" -/// response as opposite to *INTERNAL SERVER ERROR* which is defined by -/// default. -/// -/// ```rust -/// # use std::io; -/// # use actix_http::*; -/// -/// fn index(req: Request) -> Result<&'static str> { -/// Err(error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "error"))) -/// } -/// ``` -pub struct InternalError { - cause: T, - status: InternalErrorType, -} - -enum InternalErrorType { - Status(StatusCode), - Response(RefCell>), -} - -impl InternalError { - /// Create `InternalError` instance - pub fn new(cause: T, status: StatusCode) -> Self { - InternalError { - cause, - status: InternalErrorType::Status(status), - } - } - - /// Create `InternalError` with predefined `Response`. - pub fn from_response(cause: T, response: Response) -> Self { - InternalError { - cause, - status: InternalErrorType::Response(RefCell::new(Some(response))), - } - } -} - -impl fmt::Debug for InternalError -where - T: fmt::Debug + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.cause, f) - } -} - -impl fmt::Display for InternalError -where - T: fmt::Display + 'static, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.cause, f) - } -} - -impl ResponseError for InternalError -where - T: fmt::Debug + fmt::Display + 'static, -{ - fn status_code(&self) -> StatusCode { - match self.status { - InternalErrorType::Status(st) => st, - InternalErrorType::Response(ref resp) => { - if let Some(resp) = resp.borrow().as_ref() { - resp.head().status - } else { - StatusCode::INTERNAL_SERVER_ERROR - } - } - } - } - - fn error_response(&self) -> Response { - match self.status { - InternalErrorType::Status(st) => { - let mut res = Response::new(st); - let mut buf = BytesMut::new(); - let _ = write!(Writer(&mut buf), "{}", self); - res.headers_mut().insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain; charset=utf-8"), - ); - res.set_body(Body::from(buf)) - } - InternalErrorType::Response(ref resp) => { - if let Some(resp) = resp.borrow_mut().take() { - resp - } else { - Response::new(StatusCode::INTERNAL_SERVER_ERROR) - } - } - } - } -} - -/// Helper function that creates wrapper of any error and generate *BAD -/// REQUEST* response. -#[allow(non_snake_case)] -pub fn ErrorBadRequest(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::BAD_REQUEST).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNAUTHORIZED* response. -#[allow(non_snake_case)] -pub fn ErrorUnauthorized(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNAUTHORIZED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PAYMENT_REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorPaymentRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PAYMENT_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate *FORBIDDEN* -/// response. -#[allow(non_snake_case)] -pub fn ErrorForbidden(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::FORBIDDEN).into() -} - -/// Helper function that creates wrapper of any error and generate *NOT FOUND* -/// response. -#[allow(non_snake_case)] -pub fn ErrorNotFound(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_FOUND).into() -} - -/// Helper function that creates wrapper of any error and generate *METHOD NOT -/// ALLOWED* response. -#[allow(non_snake_case)] -pub fn ErrorMethodNotAllowed(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into() -} - -/// Helper function that creates wrapper of any error and generate *NOT -/// ACCEPTABLE* response. -#[allow(non_snake_case)] -pub fn ErrorNotAcceptable(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_ACCEPTABLE).into() -} - -/// Helper function that creates wrapper of any error and generate *PROXY -/// AUTHENTICATION REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorProxyAuthenticationRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PROXY_AUTHENTICATION_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate *REQUEST -/// TIMEOUT* response. -#[allow(non_snake_case)] -pub fn ErrorRequestTimeout(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into() -} - -/// Helper function that creates wrapper of any error and generate *CONFLICT* -/// response. -#[allow(non_snake_case)] -pub fn ErrorConflict(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::CONFLICT).into() -} - -/// Helper function that creates wrapper of any error and generate *GONE* -/// response. -#[allow(non_snake_case)] -pub fn ErrorGone(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::GONE).into() -} - -/// Helper function that creates wrapper of any error and generate *LENGTH -/// REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorLengthRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::LENGTH_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PAYLOAD TOO LARGE* response. -#[allow(non_snake_case)] -pub fn ErrorPayloadTooLarge(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PAYLOAD_TOO_LARGE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *URI TOO LONG* response. -#[allow(non_snake_case)] -pub fn ErrorUriTooLong(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::URI_TOO_LONG).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNSUPPORTED MEDIA TYPE* response. -#[allow(non_snake_case)] -pub fn ErrorUnsupportedMediaType(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNSUPPORTED_MEDIA_TYPE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *RANGE NOT SATISFIABLE* response. -#[allow(non_snake_case)] -pub fn ErrorRangeNotSatisfiable(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::RANGE_NOT_SATISFIABLE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *IM A TEAPOT* response. -#[allow(non_snake_case)] -pub fn ErrorImATeapot(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::IM_A_TEAPOT).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *MISDIRECTED REQUEST* response. -#[allow(non_snake_case)] -pub fn ErrorMisdirectedRequest(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::MISDIRECTED_REQUEST).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNPROCESSABLE ENTITY* response. -#[allow(non_snake_case)] -pub fn ErrorUnprocessableEntity(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNPROCESSABLE_ENTITY).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *LOCKED* response. -#[allow(non_snake_case)] -pub fn ErrorLocked(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::LOCKED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *FAILED DEPENDENCY* response. -#[allow(non_snake_case)] -pub fn ErrorFailedDependency(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::FAILED_DEPENDENCY).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UPGRADE REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorUpgradeRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UPGRADE_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PRECONDITION FAILED* response. -#[allow(non_snake_case)] -pub fn ErrorPreconditionFailed(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PRECONDITION_FAILED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *PRECONDITION REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorPreconditionRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::PRECONDITION_REQUIRED).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *TOO MANY REQUESTS* response. -#[allow(non_snake_case)] -pub fn ErrorTooManyRequests(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::TOO_MANY_REQUESTS).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *REQUEST HEADER FIELDS TOO LARGE* response. -#[allow(non_snake_case)] -pub fn ErrorRequestHeaderFieldsTooLarge(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *UNAVAILABLE FOR LEGAL REASONS* response. -#[allow(non_snake_case)] -pub fn ErrorUnavailableForLegalReasons(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS).into() -} - -/// Helper function that creates wrapper of any error and generate -/// *EXPECTATION FAILED* response. -#[allow(non_snake_case)] -pub fn ErrorExpectationFailed(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::EXPECTATION_FAILED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *INTERNAL SERVER ERROR* response. -#[allow(non_snake_case)] -pub fn ErrorInternalServerError(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *NOT IMPLEMENTED* response. -#[allow(non_snake_case)] -pub fn ErrorNotImplemented(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_IMPLEMENTED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *BAD GATEWAY* response. -#[allow(non_snake_case)] -pub fn ErrorBadGateway(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::BAD_GATEWAY).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *SERVICE UNAVAILABLE* response. -#[allow(non_snake_case)] -pub fn ErrorServiceUnavailable(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::SERVICE_UNAVAILABLE).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *GATEWAY TIMEOUT* response. -#[allow(non_snake_case)] -pub fn ErrorGatewayTimeout(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *HTTP VERSION NOT SUPPORTED* response. -#[allow(non_snake_case)] -pub fn ErrorHttpVersionNotSupported(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::HTTP_VERSION_NOT_SUPPORTED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *VARIANT ALSO NEGOTIATES* response. -#[allow(non_snake_case)] -pub fn ErrorVariantAlsoNegotiates(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::VARIANT_ALSO_NEGOTIATES).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *INSUFFICIENT STORAGE* response. -#[allow(non_snake_case)] -pub fn ErrorInsufficientStorage(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::INSUFFICIENT_STORAGE).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *LOOP DETECTED* response. -#[allow(non_snake_case)] -pub fn ErrorLoopDetected(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::LOOP_DETECTED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *NOT EXTENDED* response. -#[allow(non_snake_case)] -pub fn ErrorNotExtended(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NOT_EXTENDED).into() -} - -/// Helper function that creates wrapper of any error and -/// generate *NETWORK AUTHENTICATION REQUIRED* response. -#[allow(non_snake_case)] -pub fn ErrorNetworkAuthenticationRequired(err: T) -> Error -where - T: fmt::Debug + fmt::Display + 'static, -{ - InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() -} - -#[cfg(feature = "failure")] -/// Compatibility for `failure::Error` -impl ResponseError for fail_ure::Error {} - -#[cfg(test)] -mod tests { - use super::*; - use http::{Error as HttpError, StatusCode}; - use httparse; - use std::io; - - #[test] - fn test_into_response() { - let resp: Response = ParseError::Incomplete.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); - let resp: Response = err.error_response(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } - - #[test] - fn test_cookie_parse() { - let resp: Response = CookieParseError::EmptyName.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } - - #[test] - fn test_as_response() { - let orig = io::Error::new(io::ErrorKind::Other, "other"); - let e: Error = ParseError::Io(orig).into(); - assert_eq!(format!("{}", e.as_response_error()), "IO error: other"); - } - - #[test] - fn test_error_cause() { - let orig = io::Error::new(io::ErrorKind::Other, "other"); - let desc = orig.to_string(); - let e = Error::from(orig); - assert_eq!(format!("{}", e.as_response_error()), desc); - } - - #[test] - fn test_error_display() { - let orig = io::Error::new(io::ErrorKind::Other, "other"); - let desc = orig.to_string(); - let e = Error::from(orig); - assert_eq!(format!("{}", e), desc); - } - - #[test] - fn test_error_http_response() { - let orig = io::Error::new(io::ErrorKind::Other, "other"); - let e = Error::from(orig); - let resp: Response = e.into(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } - - #[test] - fn test_payload_error() { - let err: PayloadError = - io::Error::new(io::ErrorKind::Other, "ParseError").into(); - assert!(format!("{}", err).contains("ParseError")); - - let err = PayloadError::Incomplete(None); - assert_eq!( - format!("{}", err), - "A payload reached EOF, but is not complete. With error: None" - ); - } - - macro_rules! from { - ($from:expr => $error:pat) => { - match ParseError::from($from) { - e @ $error => { - assert!(format!("{}", e).len() >= 5); - } - e => unreachable!("{:?}", e), - } - }; - } - - macro_rules! from_and_cause { - ($from:expr => $error:pat) => { - match ParseError::from($from) { - e @ $error => { - let desc = format!("{}", e); - assert_eq!(desc, format!("IO error: {}", $from)); - } - _ => unreachable!("{:?}", $from), - } - }; - } - - #[test] - fn test_from() { - from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => ParseError::Io(..)); - from!(httparse::Error::HeaderName => ParseError::Header); - from!(httparse::Error::HeaderName => ParseError::Header); - from!(httparse::Error::HeaderValue => ParseError::Header); - from!(httparse::Error::NewLine => ParseError::Header); - from!(httparse::Error::Status => ParseError::Status); - from!(httparse::Error::Token => ParseError::Header); - from!(httparse::Error::TooManyHeaders => ParseError::TooLarge); - from!(httparse::Error::Version => ParseError::Version); - } - - #[test] - fn test_internal_error() { - let err = - InternalError::from_response(ParseError::Method, Response::Ok().into()); - let resp: Response = err.error_response(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_error_casting() { - let err = PayloadError::Overflow; - let resp_err: &dyn ResponseError = &err; - let err = resp_err.downcast_ref::().unwrap(); - assert_eq!(err.to_string(), "A payload reached size limit."); - let not_err = resp_err.downcast_ref::(); - assert!(not_err.is_none()); - } - - #[test] - fn test_error_helpers() { - let r: Response = ErrorBadRequest("err").into(); - assert_eq!(r.status(), StatusCode::BAD_REQUEST); - - let r: Response = ErrorUnauthorized("err").into(); - assert_eq!(r.status(), StatusCode::UNAUTHORIZED); - - let r: Response = ErrorPaymentRequired("err").into(); - assert_eq!(r.status(), StatusCode::PAYMENT_REQUIRED); - - let r: Response = ErrorForbidden("err").into(); - assert_eq!(r.status(), StatusCode::FORBIDDEN); - - let r: Response = ErrorNotFound("err").into(); - assert_eq!(r.status(), StatusCode::NOT_FOUND); - - let r: Response = ErrorMethodNotAllowed("err").into(); - assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED); - - let r: Response = ErrorNotAcceptable("err").into(); - assert_eq!(r.status(), StatusCode::NOT_ACCEPTABLE); - - let r: Response = ErrorProxyAuthenticationRequired("err").into(); - assert_eq!(r.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED); - - let r: Response = ErrorRequestTimeout("err").into(); - assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT); - - let r: Response = ErrorConflict("err").into(); - assert_eq!(r.status(), StatusCode::CONFLICT); - - let r: Response = ErrorGone("err").into(); - assert_eq!(r.status(), StatusCode::GONE); - - let r: Response = ErrorLengthRequired("err").into(); - assert_eq!(r.status(), StatusCode::LENGTH_REQUIRED); - - let r: Response = ErrorPreconditionFailed("err").into(); - assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED); - - let r: Response = ErrorPayloadTooLarge("err").into(); - assert_eq!(r.status(), StatusCode::PAYLOAD_TOO_LARGE); - - let r: Response = ErrorUriTooLong("err").into(); - assert_eq!(r.status(), StatusCode::URI_TOO_LONG); - - let r: Response = ErrorUnsupportedMediaType("err").into(); - assert_eq!(r.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); - - let r: Response = ErrorRangeNotSatisfiable("err").into(); - assert_eq!(r.status(), StatusCode::RANGE_NOT_SATISFIABLE); - - let r: Response = ErrorExpectationFailed("err").into(); - assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED); - - let r: Response = ErrorImATeapot("err").into(); - assert_eq!(r.status(), StatusCode::IM_A_TEAPOT); - - let r: Response = ErrorMisdirectedRequest("err").into(); - assert_eq!(r.status(), StatusCode::MISDIRECTED_REQUEST); - - let r: Response = ErrorUnprocessableEntity("err").into(); - assert_eq!(r.status(), StatusCode::UNPROCESSABLE_ENTITY); - - let r: Response = ErrorLocked("err").into(); - assert_eq!(r.status(), StatusCode::LOCKED); - - let r: Response = ErrorFailedDependency("err").into(); - assert_eq!(r.status(), StatusCode::FAILED_DEPENDENCY); - - let r: Response = ErrorUpgradeRequired("err").into(); - assert_eq!(r.status(), StatusCode::UPGRADE_REQUIRED); - - let r: Response = ErrorPreconditionRequired("err").into(); - assert_eq!(r.status(), StatusCode::PRECONDITION_REQUIRED); - - let r: Response = ErrorTooManyRequests("err").into(); - assert_eq!(r.status(), StatusCode::TOO_MANY_REQUESTS); - - let r: Response = ErrorRequestHeaderFieldsTooLarge("err").into(); - assert_eq!(r.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE); - - let r: Response = ErrorUnavailableForLegalReasons("err").into(); - assert_eq!(r.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS); - - let r: Response = ErrorInternalServerError("err").into(); - assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR); - - let r: Response = ErrorNotImplemented("err").into(); - assert_eq!(r.status(), StatusCode::NOT_IMPLEMENTED); - - let r: Response = ErrorBadGateway("err").into(); - assert_eq!(r.status(), StatusCode::BAD_GATEWAY); - - let r: Response = ErrorServiceUnavailable("err").into(); - assert_eq!(r.status(), StatusCode::SERVICE_UNAVAILABLE); - - let r: Response = ErrorGatewayTimeout("err").into(); - assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT); - - let r: Response = ErrorHttpVersionNotSupported("err").into(); - assert_eq!(r.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED); - - let r: Response = ErrorVariantAlsoNegotiates("err").into(); - assert_eq!(r.status(), StatusCode::VARIANT_ALSO_NEGOTIATES); - - let r: Response = ErrorInsufficientStorage("err").into(); - assert_eq!(r.status(), StatusCode::INSUFFICIENT_STORAGE); - - let r: Response = ErrorLoopDetected("err").into(); - assert_eq!(r.status(), StatusCode::LOOP_DETECTED); - - let r: Response = ErrorNotExtended("err").into(); - assert_eq!(r.status(), StatusCode::NOT_EXTENDED); - - let r: Response = ErrorNetworkAuthenticationRequired("err").into(); - assert_eq!(r.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED); - } -} diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs deleted file mode 100644 index d85ca184d..000000000 --- a/actix-http/src/extensions.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::any::{Any, TypeId}; -use std::fmt; - -use fxhash::FxHashMap; - -#[derive(Default)] -/// A type map of request extensions. -pub struct Extensions { - map: FxHashMap>, -} - -impl Extensions { - /// Create an empty `Extensions`. - #[inline] - pub fn new() -> Extensions { - Extensions { - map: FxHashMap::default(), - } - } - - /// Insert a type into this `Extensions`. - /// - /// If a extension of this type already existed, it will - /// be returned. - pub fn insert(&mut self, val: T) { - self.map.insert(TypeId::of::(), Box::new(val)); - } - - /// Check if container contains entry - pub fn contains(&self) -> bool { - self.map.get(&TypeId::of::()).is_some() - } - - /// Get a reference to a type previously inserted on this `Extensions`. - pub fn get(&self) -> Option<&T> { - self.map - .get(&TypeId::of::()) - .and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref()) - } - - /// Get a mutable reference to a type previously inserted on this `Extensions`. - pub fn get_mut(&mut self) -> Option<&mut T> { - self.map - .get_mut(&TypeId::of::()) - .and_then(|boxed| (&mut **boxed as &mut (dyn Any + 'static)).downcast_mut()) - } - - /// Remove a type from this `Extensions`. - /// - /// If a extension of this type existed, it will be returned. - pub fn remove(&mut self) -> Option { - self.map.remove(&TypeId::of::()).and_then(|boxed| { - (boxed as Box) - .downcast() - .ok() - .map(|boxed| *boxed) - }) - } - - /// Clear the `Extensions` of all inserted extensions. - #[inline] - pub fn clear(&mut self) { - self.map.clear(); - } -} - -impl fmt::Debug for Extensions { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Extensions").finish() - } -} - -#[test] -fn test_extensions() { - #[derive(Debug, PartialEq)] - struct MyType(i32); - - let mut extensions = Extensions::new(); - - extensions.insert(5i32); - extensions.insert(MyType(10)); - - assert_eq!(extensions.get(), Some(&5i32)); - assert_eq!(extensions.get_mut(), Some(&mut 5i32)); - - assert_eq!(extensions.remove::(), Some(5i32)); - assert!(extensions.get::().is_none()); - - assert_eq!(extensions.get::(), None); - assert_eq!(extensions.get(), Some(&MyType(10))); -} diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs deleted file mode 100644 index bcfc18cde..000000000 --- a/actix-http/src/h1/client.rs +++ /dev/null @@ -1,238 +0,0 @@ -use std::io; - -use actix_codec::{Decoder, Encoder}; -use bitflags::bitflags; -use bytes::{Bytes, BytesMut}; -use http::{Method, Version}; - -use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; -use super::{decoder, encoder, reserve_readbuf}; -use super::{Message, MessageType}; -use crate::body::BodySize; -use crate::config::ServiceConfig; -use crate::error::{ParseError, PayloadError}; -use crate::message::{ConnectionType, RequestHeadType, ResponseHead}; - -bitflags! { - struct Flags: u8 { - const HEAD = 0b0000_0001; - const KEEPALIVE_ENABLED = 0b0000_1000; - const STREAM = 0b0001_0000; - } -} - -/// HTTP/1 Codec -pub struct ClientCodec { - inner: ClientCodecInner, -} - -/// HTTP/1 Payload Codec -pub struct ClientPayloadCodec { - inner: ClientCodecInner, -} - -struct ClientCodecInner { - config: ServiceConfig, - decoder: decoder::MessageDecoder, - payload: Option, - version: Version, - ctype: ConnectionType, - - // encoder part - flags: Flags, - encoder: encoder::MessageEncoder, -} - -impl Default for ClientCodec { - fn default() -> Self { - ClientCodec::new(ServiceConfig::default()) - } -} - -impl ClientCodec { - /// Create HTTP/1 codec. - /// - /// `keepalive_enabled` how response `connection` header get generated. - pub fn new(config: ServiceConfig) -> Self { - let flags = if config.keep_alive_enabled() { - Flags::KEEPALIVE_ENABLED - } else { - Flags::empty() - }; - ClientCodec { - inner: ClientCodecInner { - config, - decoder: decoder::MessageDecoder::default(), - payload: None, - version: Version::HTTP_11, - ctype: ConnectionType::Close, - - flags, - encoder: encoder::MessageEncoder::default(), - }, - } - } - - /// Check if request is upgrade - pub fn upgrade(&self) -> bool { - self.inner.ctype == ConnectionType::Upgrade - } - - /// Check if last response is keep-alive - pub fn keepalive(&self) -> bool { - self.inner.ctype == ConnectionType::KeepAlive - } - - /// Check last request's message type - pub fn message_type(&self) -> MessageType { - if self.inner.flags.contains(Flags::STREAM) { - MessageType::Stream - } else if self.inner.payload.is_none() { - MessageType::None - } else { - MessageType::Payload - } - } - - /// Convert message codec to a payload codec - pub fn into_payload_codec(self) -> ClientPayloadCodec { - ClientPayloadCodec { inner: self.inner } - } -} - -impl ClientPayloadCodec { - /// Check if last response is keep-alive - pub fn keepalive(&self) -> bool { - self.inner.ctype == ConnectionType::KeepAlive - } - - /// Transform payload codec to a message codec - pub fn into_message_codec(self) -> ClientCodec { - ClientCodec { inner: self.inner } - } -} - -impl Decoder for ClientCodec { - type Item = ResponseHead; - type Error = ParseError; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set"); - - if let Some((req, payload)) = self.inner.decoder.decode(src)? { - if let Some(ctype) = req.ctype() { - // do not use peer's keep-alive - self.inner.ctype = if ctype == ConnectionType::KeepAlive { - self.inner.ctype - } else { - ctype - }; - } - - if !self.inner.flags.contains(Flags::HEAD) { - match payload { - PayloadType::None => self.inner.payload = None, - PayloadType::Payload(pl) => self.inner.payload = Some(pl), - PayloadType::Stream(pl) => { - self.inner.payload = Some(pl); - self.inner.flags.insert(Flags::STREAM); - } - } - } else { - self.inner.payload = None; - } - reserve_readbuf(src); - Ok(Some(req)) - } else { - Ok(None) - } - } -} - -impl Decoder for ClientPayloadCodec { - type Item = Option; - type Error = PayloadError; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - debug_assert!( - self.inner.payload.is_some(), - "Payload decoder is not specified" - ); - - Ok(match self.inner.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => { - reserve_readbuf(src); - Some(Some(chunk)) - } - Some(PayloadItem::Eof) => { - self.inner.payload.take(); - Some(None) - } - None => None, - }) - } -} - -impl Encoder for ClientCodec { - type Item = Message<(RequestHeadType, BodySize)>; - type Error = io::Error; - - fn encode( - &mut self, - item: Self::Item, - dst: &mut BytesMut, - ) -> Result<(), Self::Error> { - match item { - Message::Item((mut head, length)) => { - let inner = &mut self.inner; - inner.version = head.as_ref().version; - inner - .flags - .set(Flags::HEAD, head.as_ref().method == Method::HEAD); - - // connection status - inner.ctype = match head.as_ref().connection_type() { - ConnectionType::KeepAlive => { - if inner.flags.contains(Flags::KEEPALIVE_ENABLED) { - ConnectionType::KeepAlive - } else { - ConnectionType::Close - } - } - ConnectionType::Upgrade => ConnectionType::Upgrade, - ConnectionType::Close => ConnectionType::Close, - }; - - inner.encoder.encode( - dst, - &mut head, - false, - false, - inner.version, - length, - inner.ctype, - &inner.config, - )?; - } - Message::Chunk(Some(bytes)) => { - self.inner.encoder.encode_chunk(bytes.as_ref(), dst)?; - } - Message::Chunk(None) => { - self.inner.encoder.encode_eof(dst)?; - } - } - Ok(()) - } -} - -pub struct Writer<'a>(pub &'a mut BytesMut); - -impl<'a> io::Write for Writer<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs deleted file mode 100644 index de2af9ee7..000000000 --- a/actix-http/src/h1/codec.rs +++ /dev/null @@ -1,240 +0,0 @@ -use std::{fmt, io}; - -use actix_codec::{Decoder, Encoder}; -use bitflags::bitflags; -use bytes::BytesMut; -use http::{Method, Version}; - -use super::decoder::{PayloadDecoder, PayloadItem, PayloadType}; -use super::{decoder, encoder}; -use super::{Message, MessageType}; -use crate::body::BodySize; -use crate::config::ServiceConfig; -use crate::error::ParseError; -use crate::message::ConnectionType; -use crate::request::Request; -use crate::response::Response; - -bitflags! { - struct Flags: u8 { - const HEAD = 0b0000_0001; - const KEEPALIVE_ENABLED = 0b0000_0010; - const STREAM = 0b0000_0100; - } -} - -/// HTTP/1 Codec -pub struct Codec { - config: ServiceConfig, - decoder: decoder::MessageDecoder, - payload: Option, - version: Version, - ctype: ConnectionType, - - // encoder part - flags: Flags, - encoder: encoder::MessageEncoder>, -} - -impl Default for Codec { - fn default() -> Self { - Codec::new(ServiceConfig::default()) - } -} - -impl fmt::Debug for Codec { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "h1::Codec({:?})", self.flags) - } -} - -impl Codec { - /// Create HTTP/1 codec. - /// - /// `keepalive_enabled` how response `connection` header get generated. - pub fn new(config: ServiceConfig) -> Self { - let flags = if config.keep_alive_enabled() { - Flags::KEEPALIVE_ENABLED - } else { - Flags::empty() - }; - Codec { - config, - flags, - decoder: decoder::MessageDecoder::default(), - payload: None, - version: Version::HTTP_11, - ctype: ConnectionType::Close, - encoder: encoder::MessageEncoder::default(), - } - } - - #[inline] - /// Check if request is upgrade - pub fn upgrade(&self) -> bool { - self.ctype == ConnectionType::Upgrade - } - - #[inline] - /// Check if last response is keep-alive - pub fn keepalive(&self) -> bool { - self.ctype == ConnectionType::KeepAlive - } - - #[inline] - /// Check if keep-alive enabled on server level - pub fn keepalive_enabled(&self) -> bool { - self.flags.contains(Flags::KEEPALIVE_ENABLED) - } - - #[inline] - /// Check last request's message type - pub fn message_type(&self) -> MessageType { - if self.flags.contains(Flags::STREAM) { - MessageType::Stream - } else if self.payload.is_none() { - MessageType::None - } else { - MessageType::Payload - } - } - - #[inline] - pub fn config(&self) -> &ServiceConfig { - &self.config - } -} - -impl Decoder for Codec { - type Item = Message; - type Error = ParseError; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - if self.payload.is_some() { - Ok(match self.payload.as_mut().unwrap().decode(src)? { - Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), - Some(PayloadItem::Eof) => { - self.payload.take(); - Some(Message::Chunk(None)) - } - None => None, - }) - } else if let Some((req, payload)) = self.decoder.decode(src)? { - let head = req.head(); - self.flags.set(Flags::HEAD, head.method == Method::HEAD); - self.version = head.version; - self.ctype = head.connection_type(); - if self.ctype == ConnectionType::KeepAlive - && !self.flags.contains(Flags::KEEPALIVE_ENABLED) - { - self.ctype = ConnectionType::Close - } - match payload { - PayloadType::None => self.payload = None, - PayloadType::Payload(pl) => self.payload = Some(pl), - PayloadType::Stream(pl) => { - self.payload = Some(pl); - self.flags.insert(Flags::STREAM); - } - } - Ok(Some(Message::Item(req))) - } else { - Ok(None) - } - } -} - -impl Encoder for Codec { - type Item = Message<(Response<()>, BodySize)>; - type Error = io::Error; - - fn encode( - &mut self, - item: Self::Item, - dst: &mut BytesMut, - ) -> Result<(), Self::Error> { - match item { - Message::Item((mut res, length)) => { - // set response version - res.head_mut().version = self.version; - - // connection status - self.ctype = if let Some(ct) = res.head().ctype() { - if ct == ConnectionType::KeepAlive { - self.ctype - } else { - ct - } - } else { - self.ctype - }; - - // encode message - self.encoder.encode( - dst, - &mut res, - self.flags.contains(Flags::HEAD), - self.flags.contains(Flags::STREAM), - self.version, - length, - self.ctype, - &self.config, - )?; - // self.headers_size = (dst.len() - len) as u32; - } - Message::Chunk(Some(bytes)) => { - self.encoder.encode_chunk(bytes.as_ref(), dst)?; - } - Message::Chunk(None) => { - self.encoder.encode_eof(dst)?; - } - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use bytes::BytesMut; - use http::Method; - - use super::*; - use crate::httpmessage::HttpMessage; - - #[test] - fn test_http_request_chunked_payload_and_next_message() { - let mut codec = Codec::default(); - - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let item = codec.decode(&mut buf).unwrap().unwrap(); - let req = item.message(); - - assert_eq!(req.method(), Method::GET); - assert!(req.chunked().unwrap()); - - buf.extend( - b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ - POST /test2 HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n" - .iter(), - ); - - let msg = codec.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"data"); - - let msg = codec.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"line"); - - let msg = codec.decode(&mut buf).unwrap().unwrap(); - assert!(msg.eof()); - - // decode next message - let item = codec.decode(&mut buf).unwrap().unwrap(); - let req = item.message(); - assert_eq!(*req.method(), Method::POST); - assert!(req.chunked().unwrap()); - } -} diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs deleted file mode 100644 index e113fd52d..000000000 --- a/actix-http/src/h1/decoder.rs +++ /dev/null @@ -1,1224 +0,0 @@ -use std::convert::TryFrom; -use std::io; -use std::marker::PhantomData; -use std::mem::MaybeUninit; -use std::task::Poll; - -use actix_codec::Decoder; -use bytes::{Buf, Bytes, BytesMut}; -use http::header::{HeaderName, HeaderValue}; -use http::{header, Method, StatusCode, Uri, Version}; -use httparse; -use log::{debug, error, trace}; - -use crate::error::ParseError; -use crate::header::HeaderMap; -use crate::message::{ConnectionType, ResponseHead}; -use crate::request::Request; - -const MAX_BUFFER_SIZE: usize = 131_072; -const MAX_HEADERS: usize = 96; - -/// Incoming messagd decoder -pub(crate) struct MessageDecoder(PhantomData); - -#[derive(Debug)] -/// Incoming request type -pub(crate) enum PayloadType { - None, - Payload(PayloadDecoder), - Stream(PayloadDecoder), -} - -impl Default for MessageDecoder { - fn default() -> Self { - MessageDecoder(PhantomData) - } -} - -impl Decoder for MessageDecoder { - type Item = (T, PayloadType); - type Error = ParseError; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - T::decode(src) - } -} - -pub(crate) enum PayloadLength { - Payload(PayloadType), - Upgrade, - None, -} - -pub(crate) trait MessageType: Sized { - fn set_connection_type(&mut self, ctype: Option); - - fn set_expect(&mut self); - - fn headers_mut(&mut self) -> &mut HeaderMap; - - fn decode(src: &mut BytesMut) -> Result, ParseError>; - - fn set_headers( - &mut self, - slice: &Bytes, - raw_headers: &[HeaderIndex], - ) -> Result { - let mut ka = None; - let mut has_upgrade = false; - let mut expect = false; - let mut chunked = false; - let mut content_length = None; - - { - let headers = self.headers_mut(); - - for idx in raw_headers.iter() { - let name = - HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap(); - - // Unsafe: httparse check header value for valid utf-8 - let value = unsafe { - HeaderValue::from_maybe_shared_unchecked( - slice.slice(idx.value.0..idx.value.1), - ) - }; - match name { - header::CONTENT_LENGTH => { - if let Ok(s) = value.to_str() { - if let Ok(len) = s.parse::() { - if len != 0 { - content_length = Some(len); - } - } else { - debug!("illegal Content-Length: {:?}", s); - return Err(ParseError::Header); - } - } else { - debug!("illegal Content-Length: {:?}", value); - return Err(ParseError::Header); - } - } - // transfer-encoding - header::TRANSFER_ENCODING => { - if let Ok(s) = value.to_str().map(|s| s.trim()) { - chunked = s.eq_ignore_ascii_case("chunked"); - } else { - return Err(ParseError::Header); - } - } - // connection keep-alive state - header::CONNECTION => { - ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) { - if conn.eq_ignore_ascii_case("keep-alive") { - Some(ConnectionType::KeepAlive) - } else if conn.eq_ignore_ascii_case("close") { - Some(ConnectionType::Close) - } else if conn.eq_ignore_ascii_case("upgrade") { - Some(ConnectionType::Upgrade) - } else { - None - } - } else { - None - }; - } - header::UPGRADE => { - has_upgrade = true; - // check content-length, some clients (dart) - // sends "content-length: 0" with websocket upgrade - if let Ok(val) = value.to_str().map(|val| val.trim()) { - if val.eq_ignore_ascii_case("websocket") { - content_length = None; - } - } - } - header::EXPECT => { - let bytes = value.as_bytes(); - if bytes.len() >= 4 && &bytes[0..4] == b"100-" { - expect = true; - } - } - _ => (), - } - - headers.append(name, value); - } - } - self.set_connection_type(ka); - if expect { - self.set_expect() - } - - // https://tools.ietf.org/html/rfc7230#section-3.3.3 - if chunked { - // Chunked encoding - Ok(PayloadLength::Payload(PayloadType::Payload( - PayloadDecoder::chunked(), - ))) - } else if let Some(len) = content_length { - // Content-Length - Ok(PayloadLength::Payload(PayloadType::Payload( - PayloadDecoder::length(len), - ))) - } else if has_upgrade { - Ok(PayloadLength::Upgrade) - } else { - Ok(PayloadLength::None) - } - } -} - -impl MessageType for Request { - fn set_connection_type(&mut self, ctype: Option) { - if let Some(ctype) = ctype { - self.head_mut().set_connection_type(ctype); - } - } - - fn set_expect(&mut self) { - self.head_mut().set_expect(); - } - - fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().headers - } - - #[allow(clippy::uninit_assumed_init)] - fn decode(src: &mut BytesMut) -> Result, ParseError> { - // Unsafe: we read only this data only after httparse parses headers into. - // performance bump for pipeline benchmarks. - let mut headers: [HeaderIndex; MAX_HEADERS] = - unsafe { MaybeUninit::uninit().assume_init() }; - - let (len, method, uri, ver, h_len) = { - let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = - unsafe { MaybeUninit::uninit().assume_init() }; - - let mut req = httparse::Request::new(&mut parsed); - match req.parse(src)? { - httparse::Status::Complete(len) => { - let method = Method::from_bytes(req.method.unwrap().as_bytes()) - .map_err(|_| ParseError::Method)?; - let uri = Uri::try_from(req.path.unwrap())?; - let version = if req.version.unwrap() == 1 { - Version::HTTP_11 - } else { - Version::HTTP_10 - }; - HeaderIndex::record(src, req.headers, &mut headers); - - (len, method, uri, version, req.headers.len()) - } - httparse::Status::Partial => return Ok(None), - } - }; - - let mut msg = Request::new(); - - // convert headers - let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; - - // payload decoder - let decoder = match length { - PayloadLength::Payload(pl) => pl, - PayloadLength::Upgrade => { - // upgrade(websocket) - PayloadType::Stream(PayloadDecoder::eof()) - } - PayloadLength::None => { - if method == Method::CONNECT { - PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - trace!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); - } else { - PayloadType::None - } - } - }; - - let head = msg.head_mut(); - head.uri = uri; - head.method = method; - head.version = ver; - - Ok(Some((msg, decoder))) - } -} - -impl MessageType for ResponseHead { - fn set_connection_type(&mut self, ctype: Option) { - if let Some(ctype) = ctype { - ResponseHead::set_connection_type(self, ctype); - } - } - - fn set_expect(&mut self) {} - - fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers - } - - #[allow(clippy::uninit_assumed_init)] - fn decode(src: &mut BytesMut) -> Result, ParseError> { - // Unsafe: we read only this data only after httparse parses headers into. - // performance bump for pipeline benchmarks. - let mut headers: [HeaderIndex; MAX_HEADERS] = - unsafe { MaybeUninit::uninit().assume_init() }; - - let (len, ver, status, h_len) = { - let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = - unsafe { MaybeUninit::uninit().assume_init() }; - - let mut res = httparse::Response::new(&mut parsed); - match res.parse(src)? { - httparse::Status::Complete(len) => { - let version = if res.version.unwrap() == 1 { - Version::HTTP_11 - } else { - Version::HTTP_10 - }; - let status = StatusCode::from_u16(res.code.unwrap()) - .map_err(|_| ParseError::Status)?; - HeaderIndex::record(src, res.headers, &mut headers); - - (len, version, status, res.headers.len()) - } - httparse::Status::Partial => return Ok(None), - } - }; - - let mut msg = ResponseHead::new(status); - msg.version = ver; - - // convert headers - let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?; - - // message payload - let decoder = if let PayloadLength::Payload(pl) = length { - pl - } else if status == StatusCode::SWITCHING_PROTOCOLS { - // switching protocol or connect - PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); - } else { - // for HTTP/1.0 read to eof and close connection - if msg.version == Version::HTTP_10 { - msg.set_connection_type(ConnectionType::Close); - PayloadType::Payload(PayloadDecoder::eof()) - } else { - PayloadType::None - } - }; - - Ok(Some((msg, decoder))) - } -} - -#[derive(Clone, Copy)] -pub(crate) struct HeaderIndex { - pub(crate) name: (usize, usize), - pub(crate) value: (usize, usize), -} - -impl HeaderIndex { - pub(crate) fn record( - bytes: &[u8], - headers: &[httparse::Header<'_>], - indices: &mut [HeaderIndex], - ) { - let bytes_ptr = bytes.as_ptr() as usize; - for (header, indices) in headers.iter().zip(indices.iter_mut()) { - let name_start = header.name.as_ptr() as usize - bytes_ptr; - let name_end = name_start + header.name.len(); - indices.name = (name_start, name_end); - let value_start = header.value.as_ptr() as usize - bytes_ptr; - let value_end = value_start + header.value.len(); - indices.value = (value_start, value_end); - } - } -} - -#[derive(Debug, Clone, PartialEq)] -/// Http payload item -pub enum PayloadItem { - Chunk(Bytes), - Eof, -} - -/// Decoders to handle different Transfer-Encodings. -/// -/// If a message body does not include a Transfer-Encoding, it *should* -/// include a Content-Length header. -#[derive(Debug, Clone, PartialEq)] -pub struct PayloadDecoder { - kind: Kind, -} - -impl PayloadDecoder { - pub fn length(x: u64) -> PayloadDecoder { - PayloadDecoder { - kind: Kind::Length(x), - } - } - - pub fn chunked() -> PayloadDecoder { - PayloadDecoder { - kind: Kind::Chunked(ChunkedState::Size, 0), - } - } - - pub fn eof() -> PayloadDecoder { - PayloadDecoder { kind: Kind::Eof } - } -} - -#[derive(Debug, Clone, PartialEq)] -enum Kind { - /// A Reader used when a Content-Length header is passed with a positive - /// integer. - Length(u64), - /// A Reader used when Transfer-Encoding is `chunked`. - Chunked(ChunkedState, u64), - /// A Reader used for responses that don't indicate a length or chunked. - /// - /// Note: This should only used for `Response`s. It is illegal for a - /// `Request` to be made with both `Content-Length` and - /// `Transfer-Encoding: chunked` missing, as explained from the spec: - /// - /// > If a Transfer-Encoding header field is present in a response and - /// > the chunked transfer coding is not the final encoding, the - /// > message body length is determined by reading the connection until - /// > it is closed by the server. If a Transfer-Encoding header field - /// > is present in a request and the chunked transfer coding is not - /// > the final encoding, the message body length cannot be determined - /// > reliably; the server MUST respond with the 400 (Bad Request) - /// > status code and then close the connection. - Eof, -} - -#[derive(Debug, PartialEq, Clone)] -enum ChunkedState { - Size, - SizeLws, - Extension, - SizeLf, - Body, - BodyCr, - BodyLf, - EndCr, - EndLf, - End, -} - -impl Decoder for PayloadDecoder { - type Item = PayloadItem; - type Error = io::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - match self.kind { - Kind::Length(ref mut remaining) => { - if *remaining == 0 { - Ok(Some(PayloadItem::Eof)) - } else { - if src.is_empty() { - return Ok(None); - } - let len = src.len() as u64; - let buf; - if *remaining > len { - buf = src.split().freeze(); - *remaining -= len; - } else { - buf = src.split_to(*remaining as usize).freeze(); - *remaining = 0; - }; - trace!("Length read: {}", buf.len()); - Ok(Some(PayloadItem::Chunk(buf))) - } - } - Kind::Chunked(ref mut state, ref mut size) => { - loop { - let mut buf = None; - // advances the chunked state - *state = match state.step(src, size, &mut buf) { - Poll::Pending => return Ok(None), - Poll::Ready(Ok(state)) => state, - Poll::Ready(Err(e)) => return Err(e), - }; - if *state == ChunkedState::End { - trace!("End of chunked stream"); - return Ok(Some(PayloadItem::Eof)); - } - if let Some(buf) = buf { - return Ok(Some(PayloadItem::Chunk(buf))); - } - if src.is_empty() { - return Ok(None); - } - } - } - Kind::Eof => { - if src.is_empty() { - Ok(None) - } else { - Ok(Some(PayloadItem::Chunk(src.split().freeze()))) - } - } - } - } -} - -macro_rules! byte ( - ($rdr:ident) => ({ - if $rdr.len() > 0 { - let b = $rdr[0]; - $rdr.advance(1); - b - } else { - return Poll::Pending - } - }) -); - -impl ChunkedState { - fn step( - &self, - body: &mut BytesMut, - size: &mut u64, - buf: &mut Option, - ) -> Poll> { - use self::ChunkedState::*; - match *self { - Size => ChunkedState::read_size(body, size), - SizeLws => ChunkedState::read_size_lws(body), - Extension => ChunkedState::read_extension(body), - SizeLf => ChunkedState::read_size_lf(body, size), - Body => ChunkedState::read_body(body, size, buf), - BodyCr => ChunkedState::read_body_cr(body), - BodyLf => ChunkedState::read_body_lf(body), - EndCr => ChunkedState::read_end_cr(body), - EndLf => ChunkedState::read_end_lf(body), - End => Poll::Ready(Ok(ChunkedState::End)), - } - } - - fn read_size( - rdr: &mut BytesMut, - size: &mut u64, - ) -> Poll> { - let radix = 16; - match byte!(rdr) { - b @ b'0'..=b'9' => { - *size *= radix; - *size += u64::from(b - b'0'); - } - b @ b'a'..=b'f' => { - *size *= radix; - *size += u64::from(b + 10 - b'a'); - } - b @ b'A'..=b'F' => { - *size *= radix; - *size += u64::from(b + 10 - b'A'); - } - b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)), - b';' => return Poll::Ready(Ok(ChunkedState::Extension)), - b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)), - _ => { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size line: Invalid Size", - ))); - } - } - Poll::Ready(Ok(ChunkedState::Size)) - } - - fn read_size_lws(rdr: &mut BytesMut) -> Poll> { - trace!("read_size_lws"); - match byte!(rdr) { - // LWS can follow the chunk size, but no more digits can come - b'\t' | b' ' => Poll::Ready(Ok(ChunkedState::SizeLws)), - b';' => Poll::Ready(Ok(ChunkedState::Extension)), - b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)), - _ => Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size linear white space", - ))), - } - } - fn read_extension(rdr: &mut BytesMut) -> Poll> { - match byte!(rdr) { - b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)), - _ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions - } - } - fn read_size_lf( - rdr: &mut BytesMut, - size: &mut u64, - ) -> Poll> { - match byte!(rdr) { - b'\n' if *size > 0 => Poll::Ready(Ok(ChunkedState::Body)), - b'\n' if *size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)), - _ => Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk size LF", - ))), - } - } - - fn read_body( - rdr: &mut BytesMut, - rem: &mut u64, - buf: &mut Option, - ) -> Poll> { - trace!("Chunked read, remaining={:?}", rem); - - let len = rdr.len() as u64; - if len == 0 { - Poll::Ready(Ok(ChunkedState::Body)) - } else { - let slice; - if *rem > len { - slice = rdr.split().freeze(); - *rem -= len; - } else { - slice = rdr.split_to(*rem as usize).freeze(); - *rem = 0; - } - *buf = Some(slice); - if *rem > 0 { - Poll::Ready(Ok(ChunkedState::Body)) - } else { - Poll::Ready(Ok(ChunkedState::BodyCr)) - } - } - } - - fn read_body_cr(rdr: &mut BytesMut) -> Poll> { - match byte!(rdr) { - b'\r' => Poll::Ready(Ok(ChunkedState::BodyLf)), - _ => Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk body CR", - ))), - } - } - fn read_body_lf(rdr: &mut BytesMut) -> Poll> { - match byte!(rdr) { - b'\n' => Poll::Ready(Ok(ChunkedState::Size)), - _ => Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk body LF", - ))), - } - } - fn read_end_cr(rdr: &mut BytesMut) -> Poll> { - match byte!(rdr) { - b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)), - _ => Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk end CR", - ))), - } - } - fn read_end_lf(rdr: &mut BytesMut) -> Poll> { - match byte!(rdr) { - b'\n' => Poll::Ready(Ok(ChunkedState::End)), - _ => Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Invalid chunk end LF", - ))), - } - } -} - -#[cfg(test)] -mod tests { - use bytes::{Bytes, BytesMut}; - use http::{Method, Version}; - - use super::*; - use crate::error::ParseError; - use crate::http::header::{HeaderName, SET_COOKIE}; - use crate::httpmessage::HttpMessage; - - impl PayloadType { - fn unwrap(self) -> PayloadDecoder { - match self { - PayloadType::Payload(pl) => pl, - _ => panic!(), - } - } - - fn is_unhandled(&self) -> bool { - match self { - PayloadType::Stream(_) => true, - _ => false, - } - } - } - - impl PayloadItem { - fn chunk(self) -> Bytes { - match self { - PayloadItem::Chunk(chunk) => chunk, - _ => panic!("error"), - } - } - fn eof(&self) -> bool { - match *self { - PayloadItem::Eof => true, - _ => false, - } - } - } - - macro_rules! parse_ready { - ($e:expr) => {{ - match MessageDecoder::::default().decode($e) { - Ok(Some((msg, _))) => msg, - Ok(_) => unreachable!("Eof during parsing http request"), - Err(err) => unreachable!("Error during parsing http request: {:?}", err), - } - }}; - } - - macro_rules! expect_parse_err { - ($e:expr) => {{ - match MessageDecoder::::default().decode($e) { - Err(err) => match err { - ParseError::Io(_) => unreachable!("Parse error expected"), - _ => (), - }, - _ => unreachable!("Error expected"), - } - }}; - } - - #[test] - fn test_parse() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - - let mut reader = MessageDecoder::::default(); - match reader.decode(&mut buf) { - Ok(Some((req, _))) => { - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - } - Ok(_) | Err(_) => unreachable!("Error during parsing http request"), - } - } - - #[test] - fn test_parse_partial() { - let mut buf = BytesMut::from("PUT /test HTTP/1"); - - let mut reader = MessageDecoder::::default(); - assert!(reader.decode(&mut buf).unwrap().is_none()); - - buf.extend(b".1\r\n\r\n"); - let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::PUT); - assert_eq!(req.path(), "/test"); - } - - #[test] - fn test_parse_post() { - let mut buf = BytesMut::from("POST /test2 HTTP/1.0\r\n\r\n"); - - let mut reader = MessageDecoder::::default(); - let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(req.version(), Version::HTTP_10); - assert_eq!(*req.method(), Method::POST); - assert_eq!(req.path(), "/test2"); - } - - #[test] - fn test_parse_body() { - let mut buf = - BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - - let mut reader = MessageDecoder::::default(); - let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"body" - ); - } - - #[test] - fn test_parse_body_crlf() { - let mut buf = - BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody"); - - let mut reader = MessageDecoder::::default(); - let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"body" - ); - } - - #[test] - fn test_parse_partial_eof() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - let mut reader = MessageDecoder::::default(); - assert!(reader.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"\r\n"); - let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - } - - #[test] - fn test_headers_split_field() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n"); - - let mut reader = MessageDecoder::::default(); - assert! { reader.decode(&mut buf).unwrap().is_none() } - - buf.extend(b"t"); - assert! { reader.decode(&mut buf).unwrap().is_none() } - - buf.extend(b"es"); - assert! { reader.decode(&mut buf).unwrap().is_none() } - - buf.extend(b"t: value\r\n\r\n"); - let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(req.version(), Version::HTTP_11); - assert_eq!(*req.method(), Method::GET); - assert_eq!(req.path(), "/test"); - assert_eq!( - req.headers() - .get(HeaderName::try_from("test").unwrap()) - .unwrap() - .as_bytes(), - b"value" - ); - } - - #[test] - fn test_headers_multi_value() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - Set-Cookie: c1=cookie1\r\n\ - Set-Cookie: c2=cookie2\r\n\r\n", - ); - let mut reader = MessageDecoder::::default(); - let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); - - let val: Vec<_> = req - .headers() - .get_all(SET_COOKIE) - .map(|v| v.to_str().unwrap().to_owned()) - .collect(); - assert_eq!(val[1], "c1=cookie1"); - assert_eq!(val[0], "c2=cookie2"); - } - - #[test] - fn test_conn_default_1_0() { - let mut buf = BytesMut::from("GET /test HTTP/1.0\r\n\r\n"); - let req = parse_ready!(&mut buf); - - assert_eq!(req.head().connection_type(), ConnectionType::Close); - } - - #[test] - fn test_conn_default_1_1() { - let mut buf = BytesMut::from("GET /test HTTP/1.1\r\n\r\n"); - let req = parse_ready!(&mut buf); - - assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); - } - - #[test] - fn test_conn_close() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert_eq!(req.head().connection_type(), ConnectionType::Close); - - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: Close\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert_eq!(req.head().connection_type(), ConnectionType::Close); - } - - #[test] - fn test_conn_close_1_0() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: close\r\n\r\n", - ); - - let req = parse_ready!(&mut buf); - - assert_eq!(req.head().connection_type(), ConnectionType::Close); - } - - #[test] - fn test_conn_keep_alive_1_0() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: keep-alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); - - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: Keep-Alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); - } - - #[test] - fn test_conn_keep_alive_1_1() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: keep-alive\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); - } - - #[test] - fn test_conn_other_1_0() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.0\r\n\ - connection: other\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert_eq!(req.head().connection_type(), ConnectionType::Close); - } - - #[test] - fn test_conn_other_1_1() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: other\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert_eq!(req.head().connection_type(), ConnectionType::KeepAlive); - } - - #[test] - fn test_conn_upgrade() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - upgrade: websockets\r\n\ - connection: upgrade\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.upgrade()); - assert_eq!(req.head().connection_type(), ConnectionType::Upgrade); - - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - upgrade: Websockets\r\n\ - connection: Upgrade\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.upgrade()); - assert_eq!(req.head().connection_type(), ConnectionType::Upgrade); - } - - #[test] - fn test_conn_upgrade_connect_method() { - let mut buf = BytesMut::from( - "CONNECT /test HTTP/1.1\r\n\ - content-type: text/plain\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert!(req.upgrade()); - } - - #[test] - fn test_request_chunked() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - if let Ok(val) = req.chunked() { - assert!(val); - } else { - unreachable!("Error"); - } - - // type in chunked - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chnked\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - if let Ok(val) = req.chunked() { - assert!(!val); - } else { - unreachable!("Error"); - } - } - - #[test] - fn test_headers_content_length_err_1() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - content-length: line\r\n\r\n", - ); - - expect_parse_err!(&mut buf) - } - - #[test] - fn test_headers_content_length_err_2() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - content-length: -1\r\n\r\n", - ); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_invalid_header() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - test line\r\n\r\n", - ); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_invalid_name() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - test[]: line\r\n\r\n", - ); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_bad_status_line() { - let mut buf = BytesMut::from("getpath \r\n\r\n"); - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_upgrade() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - connection: upgrade\r\n\ - upgrade: websocket\r\n\r\n\ - some raw data", - ); - let mut reader = MessageDecoder::::default(); - let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - assert_eq!(req.head().connection_type(), ConnectionType::Upgrade); - assert!(req.upgrade()); - assert!(pl.is_unhandled()); - } - - #[test] - fn test_http_request_parser_utf8() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - x-test: теÑÑ‚\r\n\r\n", - ); - let req = parse_ready!(&mut buf); - - assert_eq!( - req.headers().get("x-test").unwrap().as_bytes(), - "теÑÑ‚".as_bytes() - ); - } - - #[test] - fn test_http_request_parser_two_slashes() { - let mut buf = BytesMut::from("GET //path HTTP/1.1\r\n\r\n"); - let req = parse_ready!(&mut buf); - - assert_eq!(req.path(), "//path"); - } - - #[test] - fn test_http_request_parser_bad_method() { - let mut buf = BytesMut::from("!12%()+=~$ /get HTTP/1.1\r\n\r\n"); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_parser_bad_version() { - let mut buf = BytesMut::from("GET //get HT/11\r\n\r\n"); - - expect_parse_err!(&mut buf); - } - - #[test] - fn test_http_request_chunked_payload() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let mut reader = MessageDecoder::::default(); - let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); - assert!(req.chunked().unwrap()); - - buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); - assert_eq!( - pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"data" - ); - assert_eq!( - pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(), - b"line" - ); - assert!(pl.decode(&mut buf).unwrap().unwrap().eof()); - } - - #[test] - fn test_http_request_chunked_payload_and_next_message() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - let mut reader = MessageDecoder::::default(); - let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); - assert!(req.chunked().unwrap()); - - buf.extend( - b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\ - POST /test2 HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n" - .iter(), - ); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"data"); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"line"); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert!(msg.eof()); - - let (req, _) = reader.decode(&mut buf).unwrap().unwrap(); - assert!(req.chunked().unwrap()); - assert_eq!(*req.method(), Method::POST); - assert!(req.chunked().unwrap()); - } - - #[test] - fn test_http_request_chunked_payload_chunks() { - let mut buf = BytesMut::from( - "GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n", - ); - - let mut reader = MessageDecoder::::default(); - let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); - assert!(req.chunked().unwrap()); - - buf.extend(b"4\r\n1111\r\n"); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"1111"); - - buf.extend(b"4\r\ndata\r"); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"data"); - - buf.extend(b"\n4"); - assert!(pl.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"\r"); - assert!(pl.decode(&mut buf).unwrap().is_none()); - buf.extend(b"\n"); - assert!(pl.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"li"); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"li"); - - //trailers - //buf.feed_data("test: test\r\n"); - //not_ready!(reader.parse(&mut buf, &mut readbuf)); - - buf.extend(b"ne\r\n0\r\n"); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(msg.chunk().as_ref(), b"ne"); - assert!(pl.decode(&mut buf).unwrap().is_none()); - - buf.extend(b"\r\n"); - assert!(pl.decode(&mut buf).unwrap().unwrap().eof()); - } - - #[test] - fn test_parse_chunked_payload_chunk_extension() { - let mut buf = BytesMut::from( - &"GET /test HTTP/1.1\r\n\ - transfer-encoding: chunked\r\n\r\n"[..], - ); - - let mut reader = MessageDecoder::::default(); - let (msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); - assert!(msg.chunked().unwrap()); - - buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n") - let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk(); - assert_eq!(chunk, Bytes::from_static(b"data")); - let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk(); - assert_eq!(chunk, Bytes::from_static(b"line")); - let msg = pl.decode(&mut buf).unwrap().unwrap(); - assert!(msg.eof()); - } - - #[test] - fn test_response_http10_read_until_eof() { - let mut buf = BytesMut::from(&"HTTP/1.0 200 Ok\r\n\r\ntest data"[..]); - - let mut reader = MessageDecoder::::default(); - let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap(); - let mut pl = pl.unwrap(); - - let chunk = pl.decode(&mut buf).unwrap().unwrap(); - assert_eq!(chunk, PayloadItem::Chunk(Bytes::from_static(b"test data"))); - } -} diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs deleted file mode 100644 index 6f4c09915..000000000 --- a/actix-http/src/h1/dispatcher.rs +++ /dev/null @@ -1,928 +0,0 @@ -use std::collections::VecDeque; -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, io, net}; - -use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; -use actix_rt::time::{delay_until, Delay, Instant}; -use actix_service::Service; -use bitflags::bitflags; -use bytes::{Buf, BytesMut}; -use log::{error, trace}; - -use crate::body::{Body, BodySize, MessageBody, ResponseBody}; -use crate::cloneable::CloneableService; -use crate::config::ServiceConfig; -use crate::error::{DispatchError, Error}; -use crate::error::{ParseError, PayloadError}; -use crate::helpers::DataFactory; -use crate::httpmessage::HttpMessage; -use crate::request::Request; -use crate::response::Response; - -use super::codec::Codec; -use super::payload::{Payload, PayloadSender, PayloadStatus}; -use super::{Message, MessageType}; - -const LW_BUFFER_SIZE: usize = 4096; -const HW_BUFFER_SIZE: usize = 32_768; -const MAX_PIPELINED_MESSAGES: usize = 16; - -bitflags! { - pub struct Flags: u8 { - const STARTED = 0b0000_0001; - const KEEPALIVE = 0b0000_0010; - const POLLED = 0b0000_0100; - const SHUTDOWN = 0b0000_1000; - const READ_DISCONNECT = 0b0001_0000; - const WRITE_DISCONNECT = 0b0010_0000; - const UPGRADE = 0b0100_0000; - } -} - -/// Dispatcher for HTTP/1.1 protocol -pub struct Dispatcher -where - S: Service, - S::Error: Into, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ - inner: DispatcherState, -} - -enum DispatcherState -where - S: Service, - S::Error: Into, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ - Normal(InnerDispatcher), - Upgrade(U::Future), - None, -} - -struct InnerDispatcher -where - S: Service, - S::Error: Into, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ - service: CloneableService, - expect: CloneableService, - upgrade: Option>, - on_connect: Option>, - flags: Flags, - peer_addr: Option, - error: Option, - - state: State, - payload: Option, - messages: VecDeque, - - ka_expire: Instant, - ka_timer: Option, - - io: T, - read_buf: BytesMut, - write_buf: BytesMut, - codec: Codec, -} - -enum DispatcherMessage { - Item(Request), - Upgrade(Request), - Error(Response<()>), -} - -enum State -where - S: Service, - X: Service, - B: MessageBody, -{ - None, - ExpectCall(X::Future), - ServiceCall(S::Future), - SendPayload(ResponseBody), -} - -impl State -where - S: Service, - X: Service, - B: MessageBody, -{ - fn is_empty(&self) -> bool { - if let State::None = self { - true - } else { - false - } - } - - fn is_call(&self) -> bool { - if let State::ServiceCall(_) = self { - true - } else { - false - } - } -} - -enum PollResponse { - Upgrade(Request), - DoNothing, - DrainWriteBuf, -} - -impl PartialEq for PollResponse { - fn eq(&self, other: &PollResponse) -> bool { - match self { - PollResponse::DrainWriteBuf => match other { - PollResponse::DrainWriteBuf => true, - _ => false, - }, - PollResponse::DoNothing => match other { - PollResponse::DoNothing => true, - _ => false, - }, - _ => false, - } - } -} - -impl Dispatcher -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into, - S::Response: Into>, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ - /// Create http/1 dispatcher. - pub(crate) fn new( - stream: T, - config: ServiceConfig, - service: CloneableService, - expect: CloneableService, - upgrade: Option>, - on_connect: Option>, - peer_addr: Option, - ) -> Self { - Dispatcher::with_timeout( - stream, - Codec::new(config.clone()), - config, - BytesMut::with_capacity(HW_BUFFER_SIZE), - None, - service, - expect, - upgrade, - on_connect, - peer_addr, - ) - } - - /// Create http/1 dispatcher with slow request timeout. - pub(crate) fn with_timeout( - io: T, - codec: Codec, - config: ServiceConfig, - read_buf: BytesMut, - timeout: Option, - service: CloneableService, - expect: CloneableService, - upgrade: Option>, - on_connect: Option>, - peer_addr: Option, - ) -> Self { - let keepalive = config.keep_alive_enabled(); - let flags = if keepalive { - Flags::KEEPALIVE - } else { - Flags::empty() - }; - - // keep-alive timer - let (ka_expire, ka_timer) = if let Some(delay) = timeout { - (delay.deadline(), Some(delay)) - } else if let Some(delay) = config.keep_alive_timer() { - (delay.deadline(), Some(delay)) - } else { - (config.now(), None) - }; - - Dispatcher { - inner: DispatcherState::Normal(InnerDispatcher { - write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), - payload: None, - state: State::None, - error: None, - messages: VecDeque::new(), - io, - codec, - read_buf, - service, - expect, - upgrade, - on_connect, - flags, - peer_addr, - ka_expire, - ka_timer, - }), - } - } -} - -impl InnerDispatcher -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into, - S::Response: Into>, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ - fn can_read(&self, cx: &mut Context<'_>) -> bool { - if self - .flags - .intersects(Flags::READ_DISCONNECT | Flags::UPGRADE) - { - false - } else if let Some(ref info) = self.payload { - info.need_read(cx) == PayloadStatus::Read - } else { - true - } - } - - // if checked is set to true, delay disconnect until all tasks have finished. - fn client_disconnected(&mut self) { - self.flags - .insert(Flags::READ_DISCONNECT | Flags::WRITE_DISCONNECT); - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete(None)); - } - } - - /// Flush stream - /// - /// true - got whouldblock - /// false - didnt get whouldblock - fn poll_flush(&mut self, cx: &mut Context<'_>) -> Result { - if self.write_buf.is_empty() { - return Ok(false); - } - - let len = self.write_buf.len(); - let mut written = 0; - while written < len { - match unsafe { Pin::new_unchecked(&mut self.io) } - .poll_write(cx, &self.write_buf[written..]) - { - Poll::Ready(Ok(0)) => { - return Err(DispatchError::Io(io::Error::new( - io::ErrorKind::WriteZero, - "", - ))); - } - Poll::Ready(Ok(n)) => { - written += n; - } - Poll::Pending => { - if written > 0 { - self.write_buf.advance(written); - } - return Ok(true); - } - Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)), - } - } - if written == self.write_buf.len() { - unsafe { self.write_buf.set_len(0) } - } else { - self.write_buf.advance(written); - } - Ok(false) - } - - fn send_response( - &mut self, - message: Response<()>, - body: ResponseBody, - ) -> Result, DispatchError> { - self.codec - .encode(Message::Item((message, body.size())), &mut self.write_buf) - .map_err(|err| { - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete(None)); - } - DispatchError::Io(err) - })?; - - self.flags.set(Flags::KEEPALIVE, self.codec.keepalive()); - match body.size() { - BodySize::None | BodySize::Empty => Ok(State::None), - _ => Ok(State::SendPayload(body)), - } - } - - fn send_continue(&mut self) { - self.write_buf - .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); - } - - fn poll_response( - &mut self, - cx: &mut Context<'_>, - ) -> Result { - loop { - let state = match self.state { - State::None => match self.messages.pop_front() { - Some(DispatcherMessage::Item(req)) => { - Some(self.handle_request(req, cx)?) - } - Some(DispatcherMessage::Error(res)) => { - Some(self.send_response(res, ResponseBody::Other(Body::Empty))?) - } - Some(DispatcherMessage::Upgrade(req)) => { - return Ok(PollResponse::Upgrade(req)); - } - None => None, - }, - State::ExpectCall(ref mut fut) => { - match unsafe { Pin::new_unchecked(fut) }.poll(cx) { - Poll::Ready(Ok(req)) => { - self.send_continue(); - self.state = State::ServiceCall(self.service.call(req)); - continue; - } - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); - Some(self.send_response(res, body.into_body())?) - } - Poll::Pending => None, - } - } - State::ServiceCall(ref mut fut) => { - match unsafe { Pin::new_unchecked(fut) }.poll(cx) { - Poll::Ready(Ok(res)) => { - let (res, body) = res.into().replace_body(()); - self.state = self.send_response(res, body)?; - continue; - } - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); - Some(self.send_response(res, body.into_body())?) - } - Poll::Pending => None, - } - } - State::SendPayload(ref mut stream) => { - loop { - if self.write_buf.len() < HW_BUFFER_SIZE { - match stream.poll_next(cx) { - Poll::Ready(Some(Ok(item))) => { - self.codec.encode( - Message::Chunk(Some(item)), - &mut self.write_buf, - )?; - continue; - } - Poll::Ready(None) => { - self.codec.encode( - Message::Chunk(None), - &mut self.write_buf, - )?; - self.state = State::None; - } - Poll::Ready(Some(Err(_))) => { - return Err(DispatchError::Unknown) - } - Poll::Pending => return Ok(PollResponse::DoNothing), - } - } else { - return Ok(PollResponse::DrainWriteBuf); - } - break; - } - continue; - } - }; - - // set new state - if let Some(state) = state { - self.state = state; - if !self.state.is_empty() { - continue; - } - } else { - // if read-backpressure is enabled and we consumed some data. - // we may read more data and retry - if self.state.is_call() { - if self.poll_request(cx)? { - continue; - } - } else if !self.messages.is_empty() { - continue; - } - } - break; - } - - Ok(PollResponse::DoNothing) - } - - fn handle_request( - &mut self, - req: Request, - cx: &mut Context<'_>, - ) -> Result, DispatchError> { - // Handle `EXPECT: 100-Continue` header - let req = if req.head().expect() { - let mut task = self.expect.call(req); - match unsafe { Pin::new_unchecked(&mut task) }.poll(cx) { - Poll::Ready(Ok(req)) => { - self.send_continue(); - req - } - Poll::Pending => return Ok(State::ExpectCall(task)), - Poll::Ready(Err(e)) => { - let e = e.into(); - let res: Response = e.into(); - let (res, body) = res.replace_body(()); - return self.send_response(res, body.into_body()); - } - } - } else { - req - }; - - // Call service - let mut task = self.service.call(req); - match unsafe { Pin::new_unchecked(&mut task) }.poll(cx) { - Poll::Ready(Ok(res)) => { - let (res, body) = res.into().replace_body(()); - self.send_response(res, body) - } - Poll::Pending => Ok(State::ServiceCall(task)), - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); - self.send_response(res, body.into_body()) - } - } - } - - /// Process one incoming requests - pub(self) fn poll_request( - &mut self, - cx: &mut Context<'_>, - ) -> Result { - // limit a mount of non processed requests - if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read(cx) { - return Ok(false); - } - - let mut updated = false; - loop { - match self.codec.decode(&mut self.read_buf) { - Ok(Some(msg)) => { - updated = true; - self.flags.insert(Flags::STARTED); - - match msg { - Message::Item(mut req) => { - let pl = self.codec.message_type(); - req.head_mut().peer_addr = self.peer_addr; - - // set on_connect data - if let Some(ref on_connect) = self.on_connect { - on_connect.set(&mut req.extensions_mut()); - } - - if pl == MessageType::Stream && self.upgrade.is_some() { - self.messages.push_back(DispatcherMessage::Upgrade(req)); - break; - } - if pl == MessageType::Payload || pl == MessageType::Stream { - let (ps, pl) = Payload::create(false); - let (req1, _) = - req.replace_payload(crate::Payload::H1(pl)); - req = req1; - self.payload = Some(ps); - } - - // handle request early - if self.state.is_empty() { - self.state = self.handle_request(req, cx)?; - } else { - self.messages.push_back(DispatcherMessage::Item(req)); - } - } - Message::Chunk(Some(chunk)) => { - if let Some(ref mut payload) = self.payload { - payload.feed_data(chunk); - } else { - error!( - "Internal server error: unexpected payload chunk" - ); - self.flags.insert(Flags::READ_DISCONNECT); - self.messages.push_back(DispatcherMessage::Error( - Response::InternalServerError().finish().drop_body(), - )); - self.error = Some(DispatchError::InternalError); - break; - } - } - Message::Chunk(None) => { - if let Some(mut payload) = self.payload.take() { - payload.feed_eof(); - } else { - error!("Internal server error: unexpected eof"); - self.flags.insert(Flags::READ_DISCONNECT); - self.messages.push_back(DispatcherMessage::Error( - Response::InternalServerError().finish().drop_body(), - )); - self.error = Some(DispatchError::InternalError); - break; - } - } - } - } - Ok(None) => break, - Err(ParseError::Io(e)) => { - self.client_disconnected(); - self.error = Some(DispatchError::Io(e)); - break; - } - Err(e) => { - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::EncodingCorrupted); - } - - // Malformed requests should be responded with 400 - self.messages.push_back(DispatcherMessage::Error( - Response::BadRequest().finish().drop_body(), - )); - self.flags.insert(Flags::READ_DISCONNECT); - self.error = Some(e.into()); - break; - } - } - } - - if updated && self.ka_timer.is_some() { - if let Some(expire) = self.codec.config().keep_alive_expire() { - self.ka_expire = expire; - } - } - Ok(updated) - } - - /// keep-alive timer - fn poll_keepalive(&mut self, cx: &mut Context<'_>) -> Result<(), DispatchError> { - if self.ka_timer.is_none() { - // shutdown timeout - if self.flags.contains(Flags::SHUTDOWN) { - if let Some(interval) = self.codec.config().client_disconnect_timer() { - self.ka_timer = Some(delay_until(interval)); - } else { - self.flags.insert(Flags::READ_DISCONNECT); - if let Some(mut payload) = self.payload.take() { - payload.set_error(PayloadError::Incomplete(None)); - } - return Ok(()); - } - } else { - return Ok(()); - } - } - - match Pin::new(&mut self.ka_timer.as_mut().unwrap()).poll(cx) { - Poll::Ready(()) => { - // if we get timeout during shutdown, drop connection - if self.flags.contains(Flags::SHUTDOWN) { - return Err(DispatchError::DisconnectTimeout); - } else if self.ka_timer.as_mut().unwrap().deadline() >= self.ka_expire { - // check for any outstanding tasks - if self.state.is_empty() && self.write_buf.is_empty() { - if self.flags.contains(Flags::STARTED) { - trace!("Keep-alive timeout, close connection"); - self.flags.insert(Flags::SHUTDOWN); - - // start shutdown timer - if let Some(deadline) = - self.codec.config().client_disconnect_timer() - { - if let Some(mut timer) = self.ka_timer.as_mut() { - timer.reset(deadline); - let _ = Pin::new(&mut timer).poll(cx); - } - } else { - // no shutdown timeout, drop socket - self.flags.insert(Flags::WRITE_DISCONNECT); - return Ok(()); - } - } else { - // timeout on first request (slow request) return 408 - if !self.flags.contains(Flags::STARTED) { - trace!("Slow request timeout"); - let _ = self.send_response( - Response::RequestTimeout().finish().drop_body(), - ResponseBody::Other(Body::Empty), - ); - } else { - trace!("Keep-alive connection timeout"); - } - self.flags.insert(Flags::STARTED | Flags::SHUTDOWN); - self.state = State::None; - } - } else if let Some(deadline) = - self.codec.config().keep_alive_expire() - { - if let Some(mut timer) = self.ka_timer.as_mut() { - timer.reset(deadline); - let _ = Pin::new(&mut timer).poll(cx); - } - } - } else if let Some(mut timer) = self.ka_timer.as_mut() { - timer.reset(self.ka_expire); - let _ = Pin::new(&mut timer).poll(cx); - } - } - Poll::Pending => (), - } - - Ok(()) - } -} - -impl Unpin for Dispatcher -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into, - S::Response: Into>, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ -} - -impl Future for Dispatcher -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into, - S::Response: Into>, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ - type Output = Result<(), DispatchError>; - - #[inline] - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.as_mut().inner { - DispatcherState::Normal(ref mut inner) => { - inner.poll_keepalive(cx)?; - - if inner.flags.contains(Flags::SHUTDOWN) { - if inner.flags.contains(Flags::WRITE_DISCONNECT) { - Poll::Ready(Ok(())) - } else { - // flush buffer - inner.poll_flush(cx)?; - if !inner.write_buf.is_empty() { - Poll::Pending - } else { - match Pin::new(&mut inner.io).poll_shutdown(cx) { - Poll::Ready(res) => { - Poll::Ready(res.map_err(DispatchError::from)) - } - Poll::Pending => Poll::Pending, - } - } - } - } else { - // read socket into a buf - let should_disconnect = - if !inner.flags.contains(Flags::READ_DISCONNECT) { - read_available(cx, &mut inner.io, &mut inner.read_buf)? - } else { - None - }; - - inner.poll_request(cx)?; - if let Some(true) = should_disconnect { - inner.flags.insert(Flags::READ_DISCONNECT); - if let Some(mut payload) = inner.payload.take() { - payload.feed_eof(); - } - }; - - loop { - let remaining = - inner.write_buf.capacity() - inner.write_buf.len(); - if remaining < LW_BUFFER_SIZE { - inner.write_buf.reserve(HW_BUFFER_SIZE - remaining); - } - let result = inner.poll_response(cx)?; - let drain = result == PollResponse::DrainWriteBuf; - - // switch to upgrade handler - if let PollResponse::Upgrade(req) = result { - if let DispatcherState::Normal(inner) = - std::mem::replace(&mut self.inner, DispatcherState::None) - { - let mut parts = FramedParts::with_read_buf( - inner.io, - inner.codec, - inner.read_buf, - ); - parts.write_buf = inner.write_buf; - let framed = Framed::from_parts(parts); - self.inner = DispatcherState::Upgrade( - inner.upgrade.unwrap().call((req, framed)), - ); - return self.poll(cx); - } else { - panic!() - } - } - - // we didnt get WouldBlock from write operation, - // so data get written to kernel completely (OSX) - // and we have to write again otherwise response can get stuck - if inner.poll_flush(cx)? || !drain { - break; - } - } - - // client is gone - if inner.flags.contains(Flags::WRITE_DISCONNECT) { - return Poll::Ready(Ok(())); - } - - let is_empty = inner.state.is_empty(); - - // read half is closed and we do not processing any responses - if inner.flags.contains(Flags::READ_DISCONNECT) && is_empty { - inner.flags.insert(Flags::SHUTDOWN); - } - - // keep-alive and stream errors - if is_empty && inner.write_buf.is_empty() { - if let Some(err) = inner.error.take() { - Poll::Ready(Err(err)) - } - // disconnect if keep-alive is not enabled - else if inner.flags.contains(Flags::STARTED) - && !inner.flags.intersects(Flags::KEEPALIVE) - { - inner.flags.insert(Flags::SHUTDOWN); - self.poll(cx) - } - // disconnect if shutdown - else if inner.flags.contains(Flags::SHUTDOWN) { - self.poll(cx) - } else { - Poll::Pending - } - } else { - Poll::Pending - } - } - } - DispatcherState::Upgrade(ref mut fut) => { - unsafe { Pin::new_unchecked(fut) }.poll(cx).map_err(|e| { - error!("Upgrade handler error: {}", e); - DispatchError::Upgrade - }) - } - DispatcherState::None => panic!(), - } - } -} - -fn read_available( - cx: &mut Context<'_>, - io: &mut T, - buf: &mut BytesMut, -) -> Result, io::Error> -where - T: AsyncRead + Unpin, -{ - let mut read_some = false; - loop { - let remaining = buf.capacity() - buf.len(); - if remaining < LW_BUFFER_SIZE { - buf.reserve(HW_BUFFER_SIZE - remaining); - } - - match read(cx, io, buf) { - Poll::Pending => { - return if read_some { Ok(Some(false)) } else { Ok(None) }; - } - Poll::Ready(Ok(n)) => { - if n == 0 { - return Ok(Some(true)); - } else { - read_some = true; - } - } - Poll::Ready(Err(e)) => { - return if e.kind() == io::ErrorKind::WouldBlock { - if read_some { - Ok(Some(false)) - } else { - Ok(None) - } - } else if e.kind() == io::ErrorKind::ConnectionReset && read_some { - Ok(Some(true)) - } else { - Err(e) - } - } - } - } -} - -fn read( - cx: &mut Context<'_>, - io: &mut T, - buf: &mut BytesMut, -) -> Poll> -where - T: AsyncRead + Unpin, -{ - Pin::new(io).poll_read_buf(cx, buf) -} - -#[cfg(test)] -mod tests { - use actix_service::IntoService; - use futures_util::future::{lazy, ok}; - - use super::*; - use crate::error::Error; - use crate::h1::{ExpectHandler, UpgradeHandler}; - use crate::test::TestBuffer; - - #[actix_rt::test] - async fn test_req_parse_err() { - lazy(|cx| { - let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); - - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( - buf, - ServiceConfig::default(), - CloneableService::new( - (|_| ok::<_, Error>(Response::Ok().finish())).into_service(), - ), - CloneableService::new(ExpectHandler), - None, - None, - None, - ); - match Pin::new(&mut h1).poll(cx) { - Poll::Pending => panic!(), - Poll::Ready(res) => assert!(res.is_err()), - } - - if let DispatcherState::Normal(ref inner) = h1.inner { - assert!(inner.flags.contains(Flags::READ_DISCONNECT)); - assert_eq!(&inner.io.write_buf[..26], b"HTTP/1.1 400 Bad Request\r\n"); - } - }) - .await; - } -} diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs deleted file mode 100644 index 4689906b4..000000000 --- a/actix-http/src/h1/encoder.rs +++ /dev/null @@ -1,655 +0,0 @@ -use std::io::Write; -use std::marker::PhantomData; -use std::ptr::copy_nonoverlapping; -use std::slice::from_raw_parts_mut; -use std::{cmp, io}; - -use bytes::{buf::BufMutExt, BufMut, BytesMut}; - -use crate::body::BodySize; -use crate::config::ServiceConfig; -use crate::header::map; -use crate::helpers; -use crate::http::header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; -use crate::http::{HeaderMap, StatusCode, Version}; -use crate::message::{ConnectionType, RequestHeadType}; -use crate::response::Response; - -const AVERAGE_HEADER_SIZE: usize = 30; - -#[derive(Debug)] -pub(crate) struct MessageEncoder { - pub length: BodySize, - pub te: TransferEncoding, - _t: PhantomData, -} - -impl Default for MessageEncoder { - fn default() -> Self { - MessageEncoder { - length: BodySize::None, - te: TransferEncoding::empty(), - _t: PhantomData, - } - } -} - -pub(crate) trait MessageType: Sized { - fn status(&self) -> Option; - - fn headers(&self) -> &HeaderMap; - - fn extra_headers(&self) -> Option<&HeaderMap>; - - fn camel_case(&self) -> bool { - false - } - - fn chunked(&self) -> bool; - - fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()>; - - fn encode_headers( - &mut self, - dst: &mut BytesMut, - version: Version, - mut length: BodySize, - ctype: ConnectionType, - config: &ServiceConfig, - ) -> io::Result<()> { - let chunked = self.chunked(); - let mut skip_len = length != BodySize::Stream; - let camel_case = self.camel_case(); - - // Content length - if let Some(status) = self.status() { - match status { - StatusCode::NO_CONTENT - | StatusCode::CONTINUE - | StatusCode::PROCESSING => length = BodySize::None, - StatusCode::SWITCHING_PROTOCOLS => { - skip_len = true; - length = BodySize::Stream; - } - _ => (), - } - } - match length { - BodySize::Stream => { - if chunked { - if camel_case { - dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n") - } else { - dst.put_slice(b"\r\ntransfer-encoding: chunked\r\n") - } - } else { - skip_len = false; - dst.put_slice(b"\r\n"); - } - } - BodySize::Empty => { - if camel_case { - dst.put_slice(b"\r\nContent-Length: 0\r\n"); - } else { - dst.put_slice(b"\r\ncontent-length: 0\r\n"); - } - } - BodySize::Sized(len) => helpers::write_content_length(len, dst), - BodySize::Sized64(len) => { - if camel_case { - dst.put_slice(b"\r\nContent-Length: "); - } else { - dst.put_slice(b"\r\ncontent-length: "); - } - #[allow(clippy::write_with_newline)] - write!(dst.writer(), "{}\r\n", len)?; - } - BodySize::None => dst.put_slice(b"\r\n"), - } - - // Connection - match ctype { - ConnectionType::Upgrade => dst.put_slice(b"connection: upgrade\r\n"), - ConnectionType::KeepAlive if version < Version::HTTP_11 => { - if camel_case { - dst.put_slice(b"Connection: keep-alive\r\n") - } else { - dst.put_slice(b"connection: keep-alive\r\n") - } - } - ConnectionType::Close if version >= Version::HTTP_11 => { - if camel_case { - dst.put_slice(b"Connection: close\r\n") - } else { - dst.put_slice(b"connection: close\r\n") - } - } - _ => (), - } - - // merging headers from head and extra headers. HeaderMap::new() does not allocate. - let empty_headers = HeaderMap::new(); - let extra_headers = self.extra_headers().unwrap_or(&empty_headers); - let headers = self - .headers() - .inner - .iter() - .filter(|(name, _)| !extra_headers.contains_key(*name)) - .chain(extra_headers.inner.iter()); - - // write headers - let mut pos = 0; - let mut has_date = false; - let mut remaining = dst.capacity() - dst.len(); - let mut buf = dst.bytes_mut().as_mut_ptr() as *mut u8; - for (key, value) in headers { - match *key { - CONNECTION => continue, - TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue, - DATE => { - has_date = true; - } - _ => (), - } - let k = key.as_str().as_bytes(); - match value { - map::Value::One(ref val) => { - let v = val.as_ref(); - let v_len = v.len(); - let k_len = k.len(); - let len = k_len + v_len + 4; - if len > remaining { - unsafe { - dst.advance_mut(pos); - } - pos = 0; - dst.reserve(len * 2); - remaining = dst.capacity() - dst.len(); - buf = dst.bytes_mut().as_mut_ptr() as *mut u8; - } - // use upper Camel-Case - unsafe { - if camel_case { - write_camel_case(k, from_raw_parts_mut(buf, k_len)) - } else { - write_data(k, buf, k_len) - } - buf = buf.add(k_len); - write_data(b": ", buf, 2); - buf = buf.add(2); - write_data(v, buf, v_len); - buf = buf.add(v_len); - write_data(b"\r\n", buf, 2); - buf = buf.add(2); - pos += len; - remaining -= len; - } - } - map::Value::Multi(ref vec) => { - for val in vec { - let v = val.as_ref(); - let v_len = v.len(); - let k_len = k.len(); - let len = k_len + v_len + 4; - if len > remaining { - unsafe { - dst.advance_mut(pos); - } - pos = 0; - dst.reserve(len * 2); - remaining = dst.capacity() - dst.len(); - buf = dst.bytes_mut().as_mut_ptr() as *mut u8; - } - // use upper Camel-Case - unsafe { - if camel_case { - write_camel_case(k, from_raw_parts_mut(buf, k_len)); - } else { - write_data(k, buf, k_len); - } - buf = buf.add(k_len); - write_data(b": ", buf, 2); - buf = buf.add(2); - write_data(v, buf, v_len); - buf = buf.add(v_len); - write_data(b"\r\n", buf, 2); - buf = buf.add(2); - }; - pos += len; - remaining -= len; - } - } - } - } - unsafe { - dst.advance_mut(pos); - } - - // optimized date header, set_date writes \r\n - if !has_date { - config.set_date(dst); - } else { - // msg eof - dst.extend_from_slice(b"\r\n"); - } - - Ok(()) - } -} - -impl MessageType for Response<()> { - fn status(&self) -> Option { - Some(self.head().status) - } - - fn chunked(&self) -> bool { - self.head().chunked() - } - - fn headers(&self) -> &HeaderMap { - &self.head().headers - } - - fn extra_headers(&self) -> Option<&HeaderMap> { - None - } - - fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { - let head = self.head(); - let reason = head.reason().as_bytes(); - dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE + reason.len()); - - // status line - helpers::write_status_line(head.version, head.status.as_u16(), dst); - dst.put_slice(reason); - Ok(()) - } -} - -impl MessageType for RequestHeadType { - fn status(&self) -> Option { - None - } - - fn chunked(&self) -> bool { - self.as_ref().chunked() - } - - fn camel_case(&self) -> bool { - self.as_ref().camel_case_headers() - } - - fn headers(&self) -> &HeaderMap { - self.as_ref().headers() - } - - fn extra_headers(&self) -> Option<&HeaderMap> { - self.extra_headers() - } - - fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> { - let head = self.as_ref(); - dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE); - write!( - Writer(dst), - "{} {} {}", - head.method, - head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), - match head.version { - Version::HTTP_09 => "HTTP/0.9", - Version::HTTP_10 => "HTTP/1.0", - Version::HTTP_11 => "HTTP/1.1", - Version::HTTP_2 => "HTTP/2.0", - Version::HTTP_3 => "HTTP/3.0", - _ => - return Err(io::Error::new( - io::ErrorKind::Other, - "unsupported version" - )), - } - ) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) - } -} - -impl MessageEncoder { - /// Encode message - pub fn encode_chunk(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { - self.te.encode(msg, buf) - } - - /// Encode eof - pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> { - self.te.encode_eof(buf) - } - - pub fn encode( - &mut self, - dst: &mut BytesMut, - message: &mut T, - head: bool, - stream: bool, - version: Version, - length: BodySize, - ctype: ConnectionType, - config: &ServiceConfig, - ) -> io::Result<()> { - // transfer encoding - if !head { - self.te = match length { - BodySize::Empty => TransferEncoding::empty(), - BodySize::Sized(len) => TransferEncoding::length(len as u64), - BodySize::Sized64(len) => TransferEncoding::length(len), - BodySize::Stream => { - if message.chunked() && !stream { - TransferEncoding::chunked() - } else { - TransferEncoding::eof() - } - } - BodySize::None => TransferEncoding::empty(), - }; - } else { - self.te = TransferEncoding::empty(); - } - - message.encode_status(dst)?; - message.encode_headers(dst, version, length, ctype, config) - } -} - -/// Encoders to handle different Transfer-Encodings. -#[derive(Debug)] -pub(crate) struct TransferEncoding { - kind: TransferEncodingKind, -} - -#[derive(Debug, PartialEq, Clone)] -enum TransferEncodingKind { - /// An Encoder for when Transfer-Encoding includes `chunked`. - Chunked(bool), - /// An Encoder for when Content-Length is set. - /// - /// Enforces that the body is not longer than the Content-Length header. - Length(u64), - /// An Encoder for when Content-Length is not known. - /// - /// Application decides when to stop writing. - Eof, -} - -impl TransferEncoding { - #[inline] - pub fn empty() -> TransferEncoding { - TransferEncoding { - kind: TransferEncodingKind::Length(0), - } - } - - #[inline] - pub fn eof() -> TransferEncoding { - TransferEncoding { - kind: TransferEncodingKind::Eof, - } - } - - #[inline] - pub fn chunked() -> TransferEncoding { - TransferEncoding { - kind: TransferEncodingKind::Chunked(false), - } - } - - #[inline] - pub fn length(len: u64) -> TransferEncoding { - TransferEncoding { - kind: TransferEncodingKind::Length(len), - } - } - - /// Encode message. Return `EOF` state of encoder - #[inline] - pub fn encode(&mut self, msg: &[u8], buf: &mut BytesMut) -> io::Result { - match self.kind { - TransferEncodingKind::Eof => { - let eof = msg.is_empty(); - buf.extend_from_slice(msg); - Ok(eof) - } - TransferEncodingKind::Chunked(ref mut eof) => { - if *eof { - return Ok(true); - } - - if msg.is_empty() { - *eof = true; - buf.extend_from_slice(b"0\r\n\r\n"); - } else { - writeln!(Writer(buf), "{:X}\r", msg.len()) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - - buf.reserve(msg.len() + 2); - buf.extend_from_slice(msg); - buf.extend_from_slice(b"\r\n"); - } - Ok(*eof) - } - TransferEncodingKind::Length(ref mut remaining) => { - if *remaining > 0 { - if msg.is_empty() { - return Ok(*remaining == 0); - } - let len = cmp::min(*remaining, msg.len() as u64); - - buf.extend_from_slice(&msg[..len as usize]); - - *remaining -= len as u64; - Ok(*remaining == 0) - } else { - Ok(true) - } - } - } - } - - /// Encode eof. Return `EOF` state of encoder - #[inline] - pub fn encode_eof(&mut self, buf: &mut BytesMut) -> io::Result<()> { - match self.kind { - TransferEncodingKind::Eof => Ok(()), - TransferEncodingKind::Length(rem) => { - if rem != 0 { - Err(io::Error::new(io::ErrorKind::UnexpectedEof, "")) - } else { - Ok(()) - } - } - TransferEncodingKind::Chunked(ref mut eof) => { - if !*eof { - *eof = true; - buf.extend_from_slice(b"0\r\n\r\n"); - } - Ok(()) - } - } - } -} - -struct Writer<'a>(pub &'a mut BytesMut); - -impl<'a> io::Write for Writer<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) { - copy_nonoverlapping(value.as_ptr(), buf, len); -} - -fn write_camel_case(value: &[u8], buffer: &mut [u8]) { - let mut index = 0; - let key = value; - let mut key_iter = key.iter(); - - if let Some(c) = key_iter.next() { - if *c >= b'a' && *c <= b'z' { - buffer[index] = *c ^ b' '; - index += 1; - } - } else { - return; - } - - while let Some(c) = key_iter.next() { - buffer[index] = *c; - index += 1; - if *c == b'-' { - if let Some(c) = key_iter.next() { - if *c >= b'a' && *c <= b'z' { - buffer[index] = *c ^ b' '; - index += 1; - } - } - } - } -} - -#[cfg(test)] -mod tests { - use std::rc::Rc; - - use bytes::Bytes; - use http::header::AUTHORIZATION; - - use super::*; - use crate::http::header::{HeaderValue, CONTENT_TYPE}; - use crate::RequestHead; - - #[test] - fn test_chunked_te() { - let mut bytes = BytesMut::new(); - let mut enc = TransferEncoding::chunked(); - { - assert!(!enc.encode(b"test", &mut bytes).ok().unwrap()); - assert!(enc.encode(b"", &mut bytes).ok().unwrap()); - } - assert_eq!( - bytes.split().freeze(), - Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n") - ); - } - - #[test] - fn test_camel_case() { - let mut bytes = BytesMut::with_capacity(2048); - let mut head = RequestHead::default(); - head.set_camel_case_headers(true); - head.headers.insert(DATE, HeaderValue::from_static("date")); - head.headers - .insert(CONTENT_TYPE, HeaderValue::from_static("plain/text")); - - let mut head = RequestHeadType::Owned(head); - - let _ = head.encode_headers( - &mut bytes, - Version::HTTP_11, - BodySize::Empty, - ConnectionType::Close, - &ServiceConfig::default(), - ); - let data = - String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); - assert!(data.contains("Content-Length: 0\r\n")); - assert!(data.contains("Connection: close\r\n")); - assert!(data.contains("Content-Type: plain/text\r\n")); - assert!(data.contains("Date: date\r\n")); - - let _ = head.encode_headers( - &mut bytes, - Version::HTTP_11, - BodySize::Stream, - ConnectionType::KeepAlive, - &ServiceConfig::default(), - ); - let data = - String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); - assert!(data.contains("Transfer-Encoding: chunked\r\n")); - assert!(data.contains("Content-Type: plain/text\r\n")); - assert!(data.contains("Date: date\r\n")); - - let _ = head.encode_headers( - &mut bytes, - Version::HTTP_11, - BodySize::Sized64(100), - ConnectionType::KeepAlive, - &ServiceConfig::default(), - ); - let data = - String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); - assert!(data.contains("Content-Length: 100\r\n")); - assert!(data.contains("Content-Type: plain/text\r\n")); - assert!(data.contains("Date: date\r\n")); - - let mut head = RequestHead::default(); - head.set_camel_case_headers(false); - head.headers.insert(DATE, HeaderValue::from_static("date")); - head.headers - .insert(CONTENT_TYPE, HeaderValue::from_static("plain/text")); - head.headers - .append(CONTENT_TYPE, HeaderValue::from_static("xml")); - - let mut head = RequestHeadType::Owned(head); - let _ = head.encode_headers( - &mut bytes, - Version::HTTP_11, - BodySize::Stream, - ConnectionType::KeepAlive, - &ServiceConfig::default(), - ); - let data = - String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); - assert!(data.contains("transfer-encoding: chunked\r\n")); - assert!(data.contains("content-type: xml\r\n")); - assert!(data.contains("content-type: plain/text\r\n")); - assert!(data.contains("date: date\r\n")); - } - - #[test] - fn test_extra_headers() { - let mut bytes = BytesMut::with_capacity(2048); - - let mut head = RequestHead::default(); - head.headers.insert( - AUTHORIZATION, - HeaderValue::from_static("some authorization"), - ); - - let mut extra_headers = HeaderMap::new(); - extra_headers.insert( - AUTHORIZATION, - HeaderValue::from_static("another authorization"), - ); - extra_headers.insert(DATE, HeaderValue::from_static("date")); - - let mut head = RequestHeadType::Rc(Rc::new(head), Some(extra_headers)); - - let _ = head.encode_headers( - &mut bytes, - Version::HTTP_11, - BodySize::Empty, - ConnectionType::Close, - &ServiceConfig::default(), - ); - let data = - String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); - assert!(data.contains("content-length: 0\r\n")); - assert!(data.contains("connection: close\r\n")); - assert!(data.contains("authorization: another authorization\r\n")); - assert!(data.contains("date: date\r\n")); - } -} diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs deleted file mode 100644 index 6c08df08e..000000000 --- a/actix-http/src/h1/expect.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::task::{Context, Poll}; - -use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ok, Ready}; - -use crate::error::Error; -use crate::request::Request; - -pub struct ExpectHandler; - -impl ServiceFactory for ExpectHandler { - type Config = (); - type Request = Request; - type Response = Request; - type Error = Error; - type Service = ExpectHandler; - type InitError = Error; - type Future = Ready>; - - fn new_service(&self, _: ()) -> Self::Future { - ok(ExpectHandler) - } -} - -impl Service for ExpectHandler { - type Request = Request; - type Response = Request; - type Error = Error; - type Future = Ready>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Request) -> Self::Future { - ok(req) - } -} diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs deleted file mode 100644 index 0c85f076a..000000000 --- a/actix-http/src/h1/mod.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! HTTP/1 implementation -use bytes::{Bytes, BytesMut}; - -mod client; -mod codec; -mod decoder; -mod dispatcher; -mod encoder; -mod expect; -mod payload; -mod service; -mod upgrade; -mod utils; - -pub use self::client::{ClientCodec, ClientPayloadCodec}; -pub use self::codec::Codec; -pub use self::dispatcher::Dispatcher; -pub use self::expect::ExpectHandler; -pub use self::payload::Payload; -pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; -pub use self::upgrade::UpgradeHandler; -pub use self::utils::SendResponse; - -#[derive(Debug)] -/// Codec message -pub enum Message { - /// Http message - Item(T), - /// Payload chunk - Chunk(Option), -} - -impl From for Message { - fn from(item: T) -> Self { - Message::Item(item) - } -} - -/// Incoming request type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MessageType { - None, - Payload, - Stream, -} - -const LW: usize = 2 * 1024; -const HW: usize = 32 * 1024; - -pub(crate) fn reserve_readbuf(src: &mut BytesMut) { - let cap = src.capacity(); - if cap < LW { - src.reserve(HW - cap); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::request::Request; - - impl Message { - pub fn message(self) -> Request { - match self { - Message::Item(req) => req, - _ => panic!("error"), - } - } - - pub fn chunk(self) -> Bytes { - match self { - Message::Chunk(Some(data)) => data, - _ => panic!("error"), - } - } - - pub fn eof(self) -> bool { - match self { - Message::Chunk(None) => true, - Message::Chunk(Some(_)) => false, - _ => panic!("error"), - } - } - } -} diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs deleted file mode 100644 index 6a348810c..000000000 --- a/actix-http/src/h1/payload.rs +++ /dev/null @@ -1,244 +0,0 @@ -//! Payload stream -use std::cell::RefCell; -use std::collections::VecDeque; -use std::pin::Pin; -use std::rc::{Rc, Weak}; -use std::task::{Context, Poll}; - -use actix_utils::task::LocalWaker; -use bytes::Bytes; -use futures_core::Stream; - -use crate::error::PayloadError; - -/// max buffer size 32k -pub(crate) const MAX_BUFFER_SIZE: usize = 32_768; - -#[derive(Debug, PartialEq)] -pub enum PayloadStatus { - Read, - Pause, - Dropped, -} - -/// Buffered stream of bytes chunks -/// -/// Payload stores chunks in a vector. First chunk can be received with -/// `.readany()` method. Payload stream is not thread safe. Payload does not -/// notify current task when new data is available. -/// -/// Payload stream can be used as `Response` body stream. -#[derive(Debug)] -pub struct Payload { - inner: Rc>, -} - -impl Payload { - /// Create payload stream. - /// - /// This method construct two objects responsible for bytes stream - /// generation. - /// - /// * `PayloadSender` - *Sender* side of the stream - /// - /// * `Payload` - *Receiver* side of the stream - pub fn create(eof: bool) -> (PayloadSender, Payload) { - let shared = Rc::new(RefCell::new(Inner::new(eof))); - - ( - PayloadSender { - inner: Rc::downgrade(&shared), - }, - Payload { inner: shared }, - ) - } - - /// Create empty payload - #[doc(hidden)] - pub fn empty() -> Payload { - Payload { - inner: Rc::new(RefCell::new(Inner::new(true))), - } - } - - /// Length of the data in this payload - #[cfg(test)] - pub fn len(&self) -> usize { - self.inner.borrow().len() - } - - /// Is payload empty - #[cfg(test)] - pub fn is_empty(&self) -> bool { - self.inner.borrow().len() == 0 - } - - /// Put unused data back to payload - #[inline] - pub fn unread_data(&mut self, data: Bytes) { - self.inner.borrow_mut().unread_data(data); - } - - #[inline] - pub fn readany( - &mut self, - cx: &mut Context<'_>, - ) -> Poll>> { - self.inner.borrow_mut().readany(cx) - } -} - -impl Stream for Payload { - type Item = Result; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - self.inner.borrow_mut().readany(cx) - } -} - -/// Sender part of the payload stream -pub struct PayloadSender { - inner: Weak>, -} - -impl PayloadSender { - #[inline] - pub fn set_error(&mut self, err: PayloadError) { - if let Some(shared) = self.inner.upgrade() { - shared.borrow_mut().set_error(err) - } - } - - #[inline] - pub fn feed_eof(&mut self) { - if let Some(shared) = self.inner.upgrade() { - shared.borrow_mut().feed_eof() - } - } - - #[inline] - pub fn feed_data(&mut self, data: Bytes) { - if let Some(shared) = self.inner.upgrade() { - shared.borrow_mut().feed_data(data) - } - } - - #[inline] - pub fn need_read(&self, cx: &mut Context<'_>) -> PayloadStatus { - // we check need_read only if Payload (other side) is alive, - // otherwise always return true (consume payload) - if let Some(shared) = self.inner.upgrade() { - if shared.borrow().need_read { - PayloadStatus::Read - } else { - shared.borrow_mut().io_task.register(cx.waker()); - PayloadStatus::Pause - } - } else { - PayloadStatus::Dropped - } - } -} - -#[derive(Debug)] -struct Inner { - len: usize, - eof: bool, - err: Option, - need_read: bool, - items: VecDeque, - task: LocalWaker, - io_task: LocalWaker, -} - -impl Inner { - fn new(eof: bool) -> Self { - Inner { - eof, - len: 0, - err: None, - items: VecDeque::new(), - need_read: true, - task: LocalWaker::new(), - io_task: LocalWaker::new(), - } - } - - #[inline] - fn set_error(&mut self, err: PayloadError) { - self.err = Some(err); - } - - #[inline] - fn feed_eof(&mut self) { - self.eof = true; - } - - #[inline] - fn feed_data(&mut self, data: Bytes) { - self.len += data.len(); - self.items.push_back(data); - self.need_read = self.len < MAX_BUFFER_SIZE; - if let Some(task) = self.task.take() { - task.wake() - } - } - - #[cfg(test)] - fn len(&self) -> usize { - self.len - } - - fn readany( - &mut self, - cx: &mut Context<'_>, - ) -> Poll>> { - if let Some(data) = self.items.pop_front() { - self.len -= data.len(); - self.need_read = self.len < MAX_BUFFER_SIZE; - - if self.need_read && !self.eof { - self.task.register(cx.waker()); - } - self.io_task.wake(); - Poll::Ready(Some(Ok(data))) - } else if let Some(err) = self.err.take() { - Poll::Ready(Some(Err(err))) - } else if self.eof { - Poll::Ready(None) - } else { - self.need_read = true; - self.task.register(cx.waker()); - self.io_task.wake(); - Poll::Pending - } - } - - fn unread_data(&mut self, data: Bytes) { - self.len += data.len(); - self.items.push_front(data); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use futures_util::future::poll_fn; - - #[actix_rt::test] - async fn test_unread_data() { - let (_, mut payload) = Payload::create(false); - - payload.unread_data(Bytes::from("data")); - assert!(!payload.is_empty()); - assert_eq!(payload.len(), 4); - - assert_eq!( - Bytes::from("data"), - poll_fn(|cx| payload.readany(cx)).await.unwrap().unwrap() - ); - } -} diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs deleted file mode 100644 index 4d1a1dc1b..000000000 --- a/actix-http/src/h1/service.rs +++ /dev/null @@ -1,577 +0,0 @@ -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{fmt, net}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_rt::net::TcpStream; -use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; -use futures_core::ready; -use futures_util::future::{ok, Ready}; - -use crate::body::MessageBody; -use crate::cloneable::CloneableService; -use crate::config::ServiceConfig; -use crate::error::{DispatchError, Error, ParseError}; -use crate::helpers::DataFactory; -use crate::request::Request; -use crate::response::Response; - -use super::codec::Codec; -use super::dispatcher::Dispatcher; -use super::{ExpectHandler, Message, UpgradeHandler}; - -/// `ServiceFactory` implementation for HTTP1 transport -pub struct H1Service> { - srv: S, - cfg: ServiceConfig, - expect: X, - upgrade: Option, - on_connect: Option Box>>, - _t: PhantomData<(T, B)>, -} - -impl H1Service -where - S: ServiceFactory, - S::Error: Into, - S::InitError: fmt::Debug, - S::Response: Into>, - B: MessageBody, -{ - /// Create new `HttpService` instance with config. - pub(crate) fn with_config>( - cfg: ServiceConfig, - service: F, - ) -> Self { - H1Service { - cfg, - srv: service.into_factory(), - expect: ExpectHandler, - upgrade: None, - on_connect: None, - _t: PhantomData, - } - } -} - -impl H1Service -where - S: ServiceFactory, - S::Error: Into, - S::InitError: fmt::Debug, - S::Response: Into>, - B: MessageBody, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - U: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, - U::Error: fmt::Display + Into, - U::InitError: fmt::Debug, -{ - /// Create simple tcp stream service - pub fn tcp( - self, - ) -> impl ServiceFactory< - Config = (), - Request = TcpStream, - Response = (), - Error = DispatchError, - InitError = (), - > { - pipeline_factory(|io: TcpStream| { - let peer_addr = io.peer_addr().ok(); - ok((io, peer_addr)) - }) - .and_then(self) - } -} - -#[cfg(feature = "openssl")] -mod openssl { - use super::*; - - use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream}; - use actix_tls::{openssl::HandshakeError, SslError}; - - impl H1Service, S, B, X, U> - where - S: ServiceFactory, - S::Error: Into, - S::InitError: fmt::Debug, - S::Response: Into>, - B: MessageBody, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - U: ServiceFactory< - Config = (), - Request = (Request, Framed, Codec>), - Response = (), - >, - U::Error: fmt::Display + Into, - U::InitError: fmt::Debug, - { - /// Create openssl based service - pub fn openssl( - self, - acceptor: SslAcceptor, - ) -> impl ServiceFactory< - Config = (), - Request = TcpStream, - Response = (), - Error = SslError, DispatchError>, - InitError = (), - > { - pipeline_factory( - Acceptor::new(acceptor) - .map_err(SslError::Ssl) - .map_init_err(|_| panic!()), - ) - .and_then(|io: SslStream| { - let peer_addr = io.get_ref().peer_addr().ok(); - ok((io, peer_addr)) - }) - .and_then(self.map_err(SslError::Service)) - } - } -} - -#[cfg(feature = "rustls")] -mod rustls { - use super::*; - use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream}; - use actix_tls::SslError; - use std::{fmt, io}; - - impl H1Service, S, B, X, U> - where - S: ServiceFactory, - S::Error: Into, - S::InitError: fmt::Debug, - S::Response: Into>, - B: MessageBody, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - U: ServiceFactory< - Config = (), - Request = (Request, Framed, Codec>), - Response = (), - >, - U::Error: fmt::Display + Into, - U::InitError: fmt::Debug, - { - /// Create rustls based service - pub fn rustls( - self, - config: ServerConfig, - ) -> impl ServiceFactory< - Config = (), - Request = TcpStream, - Response = (), - Error = SslError, - InitError = (), - > { - pipeline_factory( - Acceptor::new(config) - .map_err(SslError::Ssl) - .map_init_err(|_| panic!()), - ) - .and_then(|io: TlsStream| { - let peer_addr = io.get_ref().0.peer_addr().ok(); - ok((io, peer_addr)) - }) - .and_then(self.map_err(SslError::Service)) - } - } -} - -impl H1Service -where - S: ServiceFactory, - S::Error: Into, - S::Response: Into>, - S::InitError: fmt::Debug, - B: MessageBody, -{ - pub fn expect(self, expect: X1) -> H1Service - where - X1: ServiceFactory, - X1::Error: Into, - X1::InitError: fmt::Debug, - { - H1Service { - expect, - cfg: self.cfg, - srv: self.srv, - upgrade: self.upgrade, - on_connect: self.on_connect, - _t: PhantomData, - } - } - - pub fn upgrade(self, upgrade: Option) -> H1Service - where - U1: ServiceFactory), Response = ()>, - U1::Error: fmt::Display, - U1::InitError: fmt::Debug, - { - H1Service { - upgrade, - cfg: self.cfg, - srv: self.srv, - expect: self.expect, - on_connect: self.on_connect, - _t: PhantomData, - } - } - - /// Set on connect callback. - pub(crate) fn on_connect( - mut self, - f: Option Box>>, - ) -> Self { - self.on_connect = f; - self - } -} - -impl ServiceFactory for H1Service -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into, - S::Response: Into>, - S::InitError: fmt::Debug, - B: MessageBody, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - U: ServiceFactory), Response = ()>, - U::Error: fmt::Display + Into, - U::InitError: fmt::Debug, -{ - type Config = (); - type Request = (T, Option); - type Response = (); - type Error = DispatchError; - type InitError = (); - type Service = H1ServiceHandler; - type Future = H1ServiceResponse; - - fn new_service(&self, _: ()) -> Self::Future { - H1ServiceResponse { - fut: self.srv.new_service(()), - fut_ex: Some(self.expect.new_service(())), - fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), - expect: None, - upgrade: None, - on_connect: self.on_connect.clone(), - cfg: Some(self.cfg.clone()), - _t: PhantomData, - } - } -} - -#[doc(hidden)] -#[pin_project::pin_project] -pub struct H1ServiceResponse -where - S: ServiceFactory, - S::Error: Into, - S::InitError: fmt::Debug, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - U: ServiceFactory), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, -{ - #[pin] - fut: S::Future, - #[pin] - fut_ex: Option, - #[pin] - fut_upg: Option, - expect: Option, - upgrade: Option, - on_connect: Option Box>>, - cfg: Option, - _t: PhantomData<(T, B)>, -} - -impl Future for H1ServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into, - S::Response: Into>, - S::InitError: fmt::Debug, - B: MessageBody, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - U: ServiceFactory), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, -{ - type Output = Result, ()>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); - - if let Some(fut) = this.fut_ex.as_pin_mut() { - let expect = ready!(fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this = self.as_mut().project(); - *this.expect = Some(expect); - this.fut_ex.set(None); - } - - if let Some(fut) = this.fut_upg.as_pin_mut() { - let upgrade = ready!(fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this = self.as_mut().project(); - *this.upgrade = Some(upgrade); - this.fut_ex.set(None); - } - - let result = ready!(this - .fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e))); - - Poll::Ready(result.map(|service| { - let this = self.as_mut().project(); - H1ServiceHandler::new( - this.cfg.take().unwrap(), - service, - this.expect.take().unwrap(), - this.upgrade.take(), - this.on_connect.clone(), - ) - })) - } -} - -/// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler { - srv: CloneableService, - expect: CloneableService, - upgrade: Option>, - on_connect: Option Box>>, - cfg: ServiceConfig, - _t: PhantomData<(T, B)>, -} - -impl H1ServiceHandler -where - S: Service, - S::Error: Into, - S::Response: Into>, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ - fn new( - cfg: ServiceConfig, - srv: S, - expect: X, - upgrade: Option, - on_connect: Option Box>>, - ) -> H1ServiceHandler { - H1ServiceHandler { - srv: CloneableService::new(srv), - expect: CloneableService::new(expect), - upgrade: upgrade.map(CloneableService::new), - cfg, - on_connect, - _t: PhantomData, - } - } -} - -impl Service for H1ServiceHandler -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into, - S::Response: Into>, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display + Into, -{ - type Request = (T, Option); - type Response = (); - type Error = DispatchError; - type Future = Dispatcher; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let ready = self - .expect - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready(); - - let ready = self - .srv - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready() - && ready; - - let ready = if let Some(ref mut upg) = self.upgrade { - upg.poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready() - && ready - } else { - ready - }; - - if ready { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - } - - fn call(&mut self, (io, addr): Self::Request) -> Self::Future { - let on_connect = if let Some(ref on_connect) = self.on_connect { - Some(on_connect(&io)) - } else { - None - }; - - Dispatcher::new( - io, - self.cfg.clone(), - self.srv.clone(), - self.expect.clone(), - self.upgrade.clone(), - on_connect, - addr, - ) - } -} - -/// `ServiceFactory` implementation for `OneRequestService` service -#[derive(Default)] -pub struct OneRequest { - config: ServiceConfig, - _t: PhantomData, -} - -impl OneRequest -where - T: AsyncRead + AsyncWrite + Unpin, -{ - /// Create new `H1SimpleService` instance. - pub fn new() -> Self { - OneRequest { - config: ServiceConfig::default(), - _t: PhantomData, - } - } -} - -impl ServiceFactory for OneRequest -where - T: AsyncRead + AsyncWrite + Unpin, -{ - type Config = (); - type Request = T; - type Response = (Request, Framed); - type Error = ParseError; - type InitError = (); - type Service = OneRequestService; - type Future = Ready>; - - fn new_service(&self, _: ()) -> Self::Future { - ok(OneRequestService { - _t: PhantomData, - config: self.config.clone(), - }) - } -} - -/// `Service` implementation for HTTP1 transport. Reads one request and returns -/// request and framed object. -pub struct OneRequestService { - _t: PhantomData, - config: ServiceConfig, -} - -impl Service for OneRequestService -where - T: AsyncRead + AsyncWrite + Unpin, -{ - type Request = T; - type Response = (Request, Framed); - type Error = ParseError; - type Future = OneRequestServiceResponse; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - OneRequestServiceResponse { - framed: Some(Framed::new(req, Codec::new(self.config.clone()))), - } - } -} - -#[doc(hidden)] -pub struct OneRequestServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, -{ - framed: Option>, -} - -impl Future for OneRequestServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, -{ - type Output = Result<(Request, Framed), ParseError>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.framed.as_mut().unwrap().next_item(cx) { - Poll::Ready(Some(Ok(req))) => match req { - Message::Item(req) => { - Poll::Ready(Ok((req, self.framed.take().unwrap()))) - } - Message::Chunk(_) => unreachable!("Something is wrong"), - }, - Poll::Ready(Some(Err(err))) => Poll::Ready(Err(err)), - Poll::Ready(None) => Poll::Ready(Err(ParseError::Incomplete)), - Poll::Pending => Poll::Pending, - } - } -} diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs deleted file mode 100644 index 22ba99e26..000000000 --- a/actix-http/src/h1/upgrade.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::marker::PhantomData; -use std::task::{Context, Poll}; - -use actix_codec::Framed; -use actix_service::{Service, ServiceFactory}; -use futures_util::future::Ready; - -use crate::error::Error; -use crate::h1::Codec; -use crate::request::Request; - -pub struct UpgradeHandler(PhantomData); - -impl ServiceFactory for UpgradeHandler { - type Config = (); - type Request = (Request, Framed); - type Response = (); - type Error = Error; - type Service = UpgradeHandler; - type InitError = Error; - type Future = Ready>; - - fn new_service(&self, _: ()) -> Self::Future { - unimplemented!() - } -} - -impl Service for UpgradeHandler { - type Request = (Request, Framed); - type Response = (); - type Error = Error; - type Future = Ready>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _: Self::Request) -> Self::Future { - unimplemented!() - } -} diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs deleted file mode 100644 index 9ba4aa053..000000000 --- a/actix-http/src/h1/utils.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; - -use crate::body::{BodySize, MessageBody, ResponseBody}; -use crate::error::Error; -use crate::h1::{Codec, Message}; -use crate::response::Response; - -/// Send http/1 response -#[pin_project::pin_project] -pub struct SendResponse { - res: Option, BodySize)>>, - body: Option>, - framed: Option>, -} - -impl SendResponse -where - B: MessageBody, -{ - pub fn new(framed: Framed, response: Response) -> Self { - let (res, body) = response.into_parts(); - - SendResponse { - res: Some((res, body.size()).into()), - body: Some(body), - framed: Some(framed), - } - } -} - -impl Future for SendResponse -where - T: AsyncRead + AsyncWrite, - B: MessageBody, -{ - type Output = Result, Error>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - - loop { - let mut body_ready = this.body.is_some(); - let framed = this.framed.as_mut().unwrap(); - - // send body - if this.res.is_none() && this.body.is_some() { - while body_ready && this.body.is_some() && !framed.is_write_buf_full() { - match this.body.as_mut().unwrap().poll_next(cx)? { - Poll::Ready(item) => { - // body is done - if item.is_none() { - let _ = this.body.take(); - } - framed.write(Message::Chunk(item))?; - } - Poll::Pending => body_ready = false, - } - } - } - - // flush write buffer - if !framed.is_write_buf_empty() { - match framed.flush(cx)? { - Poll::Ready(_) => { - if body_ready { - continue; - } else { - return Poll::Pending; - } - } - Poll::Pending => return Poll::Pending, - } - } - - // send response - if let Some(res) = this.res.take() { - framed.write(res)?; - continue; - } - - if this.body.is_some() { - if body_ready { - continue; - } else { - return Poll::Pending; - } - } else { - break; - } - } - Poll::Ready(Ok(this.framed.take().unwrap())) - } -} diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs deleted file mode 100644 index 8b17e9479..000000000 --- a/actix-http/src/h2/dispatcher.rs +++ /dev/null @@ -1,366 +0,0 @@ -use std::convert::TryFrom; -use std::future::Future; -use std::marker::PhantomData; -use std::net; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::time::{Delay, Instant}; -use actix_service::Service; -use bytes::{Bytes, BytesMut}; -use h2::server::{Connection, SendResponse}; -use h2::SendStream; -use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; -use log::{error, trace}; - -use crate::body::{BodySize, MessageBody, ResponseBody}; -use crate::cloneable::CloneableService; -use crate::config::ServiceConfig; -use crate::error::{DispatchError, Error}; -use crate::helpers::DataFactory; -use crate::httpmessage::HttpMessage; -use crate::message::ResponseHead; -use crate::payload::Payload; -use crate::request::Request; -use crate::response::Response; - -const CHUNK_SIZE: usize = 16_384; - -/// Dispatcher for HTTP/2 protocol -#[pin_project::pin_project] -pub struct Dispatcher, B: MessageBody> -where - T: AsyncRead + AsyncWrite + Unpin, -{ - service: CloneableService, - connection: Connection, - on_connect: Option>, - config: ServiceConfig, - peer_addr: Option, - ka_expire: Instant, - ka_timer: Option, - _t: PhantomData, -} - -impl Dispatcher -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into, - // S::Future: 'static, - S::Response: Into>, - B: MessageBody, -{ - pub(crate) fn new( - service: CloneableService, - connection: Connection, - on_connect: Option>, - config: ServiceConfig, - timeout: Option, - peer_addr: Option, - ) -> Self { - // let keepalive = config.keep_alive_enabled(); - // let flags = if keepalive { - // Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED - // } else { - // Flags::empty() - // }; - - // keep-alive timer - let (ka_expire, ka_timer) = if let Some(delay) = timeout { - (delay.deadline(), Some(delay)) - } else if let Some(delay) = config.keep_alive_timer() { - (delay.deadline(), Some(delay)) - } else { - (config.now(), None) - }; - - Dispatcher { - service, - config, - peer_addr, - connection, - on_connect, - ka_expire, - ka_timer, - _t: PhantomData, - } - } -} - -impl Future for Dispatcher -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, -{ - type Output = Result<(), DispatchError>; - - #[inline] - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - - loop { - match Pin::new(&mut this.connection).poll_accept(cx) { - Poll::Ready(None) => return Poll::Ready(Ok(())), - Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())), - Poll::Ready(Some(Ok((req, res)))) => { - // update keep-alive expire - if this.ka_timer.is_some() { - if let Some(expire) = this.config.keep_alive_expire() { - this.ka_expire = expire; - } - } - - let (parts, body) = req.into_parts(); - let mut req = Request::with_payload(Payload::< - crate::payload::PayloadStream, - >::H2( - crate::h2::Payload::new(body) - )); - - let head = &mut req.head_mut(); - head.uri = parts.uri; - head.method = parts.method; - head.version = parts.version; - head.headers = parts.headers.into(); - head.peer_addr = this.peer_addr; - - // set on_connect data - if let Some(ref on_connect) = this.on_connect { - on_connect.set(&mut req.extensions_mut()); - } - - actix_rt::spawn(ServiceResponse::< - S::Future, - S::Response, - S::Error, - B, - > { - state: ServiceResponseState::ServiceCall( - this.service.call(req), - Some(res), - ), - config: this.config.clone(), - buffer: None, - _t: PhantomData, - }); - } - Poll::Pending => return Poll::Pending, - } - } - } -} - -#[pin_project::pin_project] -struct ServiceResponse { - #[pin] - state: ServiceResponseState, - config: ServiceConfig, - buffer: Option, - _t: PhantomData<(I, E)>, -} - -#[pin_project::pin_project] -enum ServiceResponseState { - ServiceCall(#[pin] F, Option>), - SendPayload(SendStream, ResponseBody), -} - -impl ServiceResponse -where - F: Future>, - E: Into, - I: Into>, - B: MessageBody, -{ - fn prepare_response( - &self, - head: &ResponseHead, - size: &mut BodySize, - ) -> http::Response<()> { - let mut has_date = false; - let mut skip_len = size != &BodySize::Stream; - - let mut res = http::Response::new(()); - *res.status_mut() = head.status; - *res.version_mut() = http::Version::HTTP_2; - - // Content length - match head.status { - http::StatusCode::NO_CONTENT - | http::StatusCode::CONTINUE - | http::StatusCode::PROCESSING => *size = BodySize::None, - http::StatusCode::SWITCHING_PROTOCOLS => { - skip_len = true; - *size = BodySize::Stream; - } - _ => (), - } - let _ = match size { - BodySize::None | BodySize::Stream => None, - BodySize::Empty => res - .headers_mut() - .insert(CONTENT_LENGTH, HeaderValue::from_static("0")), - BodySize::Sized(len) => res.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(format!("{}", len)).unwrap(), - ), - BodySize::Sized64(len) => res.headers_mut().insert( - CONTENT_LENGTH, - HeaderValue::try_from(format!("{}", len)).unwrap(), - ), - }; - - // copy headers - for (key, value) in head.headers.iter() { - match *key { - CONNECTION | TRANSFER_ENCODING => continue, // http2 specific - CONTENT_LENGTH if skip_len => continue, - DATE => has_date = true, - _ => (), - } - res.headers_mut().append(key, value.clone()); - } - - // set date header - if !has_date { - let mut bytes = BytesMut::with_capacity(29); - self.config.set_date_header(&mut bytes); - res.headers_mut().insert(DATE, unsafe { - HeaderValue::from_maybe_shared_unchecked(bytes.freeze()) - }); - } - - res - } -} - -impl Future for ServiceResponse -where - F: Future>, - E: Into, - I: Into>, - B: MessageBody, -{ - type Output = (); - - #[pin_project::project] - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); - - #[project] - match this.state.project() { - ServiceResponseState::ServiceCall(call, send) => { - match call.poll(cx) { - Poll::Ready(Ok(res)) => { - let (res, body) = res.into().replace_body(()); - - let mut send = send.take().unwrap(); - let mut size = body.size(); - let h2_res = - self.as_mut().prepare_response(res.head(), &mut size); - this = self.as_mut().project(); - - let stream = match send.send_response(h2_res, size.is_eof()) { - Err(e) => { - trace!("Error sending h2 response: {:?}", e); - return Poll::Ready(()); - } - Ok(stream) => stream, - }; - - if size.is_eof() { - Poll::Ready(()) - } else { - this.state.set(ServiceResponseState::SendPayload(stream, body)); - self.poll(cx) - } - } - Poll::Pending => Poll::Pending, - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); - - let mut send = send.take().unwrap(); - let mut size = body.size(); - let h2_res = - self.as_mut().prepare_response(res.head(), &mut size); - this = self.as_mut().project(); - - let stream = match send.send_response(h2_res, size.is_eof()) { - Err(e) => { - trace!("Error sending h2 response: {:?}", e); - return Poll::Ready(()); - } - Ok(stream) => stream, - }; - - if size.is_eof() { - Poll::Ready(()) - } else { - this.state.set(ServiceResponseState::SendPayload( - stream, - body.into_body(), - )); - self.poll(cx) - } - } - } - } - ServiceResponseState::SendPayload(ref mut stream, ref mut body) => loop { - loop { - if let Some(ref mut buffer) = this.buffer { - match stream.poll_capacity(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(None) => return Poll::Ready(()), - Poll::Ready(Some(Ok(cap))) => { - let len = buffer.len(); - let bytes = buffer.split_to(std::cmp::min(cap, len)); - - if let Err(e) = stream.send_data(bytes, false) { - warn!("{:?}", e); - return Poll::Ready(()); - } else if !buffer.is_empty() { - let cap = std::cmp::min(buffer.len(), CHUNK_SIZE); - stream.reserve_capacity(cap); - } else { - this.buffer.take(); - } - } - Poll::Ready(Some(Err(e))) => { - warn!("{:?}", e); - return Poll::Ready(()); - } - } - } else { - match body.poll_next(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(None) => { - if let Err(e) = stream.send_data(Bytes::new(), true) { - warn!("{:?}", e); - } - return Poll::Ready(()); - } - Poll::Ready(Some(Ok(chunk))) => { - stream.reserve_capacity(std::cmp::min( - chunk.len(), - CHUNK_SIZE, - )); - *this.buffer = Some(chunk); - } - Poll::Ready(Some(Err(e))) => { - error!("Response payload stream error: {:?}", e); - return Poll::Ready(()); - } - } - } - } - }, - } - } -} diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs deleted file mode 100644 index b00969227..000000000 --- a/actix-http/src/h2/mod.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! HTTP/2 implementation -use std::pin::Pin; -use std::task::{Context, Poll}; - -use bytes::Bytes; -use futures_core::Stream; -use h2::RecvStream; - -mod dispatcher; -mod service; - -pub use self::dispatcher::Dispatcher; -pub use self::service::H2Service; -use crate::error::PayloadError; - -/// H2 receive stream -pub struct Payload { - pl: RecvStream, -} - -impl Payload { - pub(crate) fn new(pl: RecvStream) -> Self { - Self { pl } - } -} - -impl Stream for Payload { - type Item = Result; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - let this = self.get_mut(); - - match Pin::new(&mut this.pl).poll_data(cx) { - Poll::Ready(Some(Ok(chunk))) => { - let len = chunk.len(); - if let Err(err) = this.pl.flow_control().release_capacity(len) { - Poll::Ready(Some(Err(err.into()))) - } else { - Poll::Ready(Some(Ok(chunk))) - } - } - Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err.into()))), - Poll::Pending => Poll::Pending, - Poll::Ready(None) => Poll::Ready(None), - } - } -} diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs deleted file mode 100644 index ff3f69faf..000000000 --- a/actix-http/src/h2/service.rs +++ /dev/null @@ -1,386 +0,0 @@ -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{net, rc}; - -use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::net::TcpStream; -use actix_service::{ - fn_factory, fn_service, pipeline_factory, IntoServiceFactory, Service, - ServiceFactory, -}; -use bytes::Bytes; -use futures_core::ready; -use futures_util::future::ok; -use h2::server::{self, Handshake}; -use log::error; - -use crate::body::MessageBody; -use crate::cloneable::CloneableService; -use crate::config::ServiceConfig; -use crate::error::{DispatchError, Error}; -use crate::helpers::DataFactory; -use crate::request::Request; -use crate::response::Response; - -use super::dispatcher::Dispatcher; - -/// `ServiceFactory` implementation for HTTP2 transport -pub struct H2Service { - srv: S, - cfg: ServiceConfig, - on_connect: Option Box>>, - _t: PhantomData<(T, B)>, -} - -impl H2Service -where - S: ServiceFactory, - S::Error: Into + 'static, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, -{ - /// Create new `HttpService` instance with config. - pub(crate) fn with_config>( - cfg: ServiceConfig, - service: F, - ) -> Self { - H2Service { - cfg, - on_connect: None, - srv: service.into_factory(), - _t: PhantomData, - } - } - - /// Set on connect callback. - pub(crate) fn on_connect( - mut self, - f: Option Box>>, - ) -> Self { - self.on_connect = f; - self - } -} - -impl H2Service -where - S: ServiceFactory, - S::Error: Into + 'static, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, -{ - /// Create simple tcp based service - pub fn tcp( - self, - ) -> impl ServiceFactory< - Config = (), - Request = TcpStream, - Response = (), - Error = DispatchError, - InitError = S::InitError, - > { - pipeline_factory(fn_factory(|| { - async { - Ok::<_, S::InitError>(fn_service(|io: TcpStream| { - let peer_addr = io.peer_addr().ok(); - ok::<_, DispatchError>((io, peer_addr)) - })) - } - })) - .and_then(self) - } -} - -#[cfg(feature = "openssl")] -mod openssl { - use actix_service::{fn_factory, fn_service}; - use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream}; - use actix_tls::{openssl::HandshakeError, SslError}; - - use super::*; - - impl H2Service, S, B> - where - S: ServiceFactory, - S::Error: Into + 'static, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, - { - /// Create ssl based service - pub fn openssl( - self, - acceptor: SslAcceptor, - ) -> impl ServiceFactory< - Config = (), - Request = TcpStream, - Response = (), - Error = SslError, DispatchError>, - InitError = S::InitError, - > { - pipeline_factory( - Acceptor::new(acceptor) - .map_err(SslError::Ssl) - .map_init_err(|_| panic!()), - ) - .and_then(fn_factory(|| { - ok::<_, S::InitError>(fn_service(|io: SslStream| { - let peer_addr = io.get_ref().peer_addr().ok(); - ok((io, peer_addr)) - })) - })) - .and_then(self.map_err(SslError::Service)) - } - } -} - -#[cfg(feature = "rustls")] -mod rustls { - use super::*; - use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream}; - use actix_tls::SslError; - use std::io; - - impl H2Service, S, B> - where - S: ServiceFactory, - S::Error: Into + 'static, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, - { - /// Create openssl based service - pub fn rustls( - self, - mut config: ServerConfig, - ) -> impl ServiceFactory< - Config = (), - Request = TcpStream, - Response = (), - Error = SslError, - InitError = S::InitError, - > { - let protos = vec!["h2".to_string().into()]; - config.set_protocols(&protos); - - pipeline_factory( - Acceptor::new(config) - .map_err(SslError::Ssl) - .map_init_err(|_| panic!()), - ) - .and_then(fn_factory(|| { - ok::<_, S::InitError>(fn_service(|io: TlsStream| { - let peer_addr = io.get_ref().0.peer_addr().ok(); - ok((io, peer_addr)) - })) - })) - .and_then(self.map_err(SslError::Service)) - } - } -} - -impl ServiceFactory for H2Service -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into + 'static, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, -{ - type Config = (); - type Request = (T, Option); - type Response = (); - type Error = DispatchError; - type InitError = S::InitError; - type Service = H2ServiceHandler; - type Future = H2ServiceResponse; - - fn new_service(&self, _: ()) -> Self::Future { - H2ServiceResponse { - fut: self.srv.new_service(()), - cfg: Some(self.cfg.clone()), - on_connect: self.on_connect.clone(), - _t: PhantomData, - } - } -} - -#[doc(hidden)] -#[pin_project::pin_project] -pub struct H2ServiceResponse { - #[pin] - fut: S::Future, - cfg: Option, - on_connect: Option Box>>, - _t: PhantomData<(T, B)>, -} - -impl Future for H2ServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into + 'static, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, -{ - type Output = Result, S::InitError>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - Poll::Ready(ready!(this.fut.poll(cx)).map(|service| { - let this = self.as_mut().project(); - H2ServiceHandler::new( - this.cfg.take().unwrap(), - this.on_connect.clone(), - service, - ) - })) - } -} - -/// `Service` implementation for http/2 transport -pub struct H2ServiceHandler { - srv: CloneableService, - cfg: ServiceConfig, - on_connect: Option Box>>, - _t: PhantomData<(T, B)>, -} - -impl H2ServiceHandler -where - S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, -{ - fn new( - cfg: ServiceConfig, - on_connect: Option Box>>, - srv: S, - ) -> H2ServiceHandler { - H2ServiceHandler { - cfg, - on_connect, - srv: CloneableService::new(srv), - _t: PhantomData, - } - } -} - -impl Service for H2ServiceHandler -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, -{ - type Request = (T, Option); - type Response = (); - type Error = DispatchError; - type Future = H2ServiceHandlerResponse; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.srv.poll_ready(cx).map_err(|e| { - let e = e.into(); - error!("Service readiness error: {:?}", e); - DispatchError::Service(e) - }) - } - - fn call(&mut self, (io, addr): Self::Request) -> Self::Future { - let on_connect = if let Some(ref on_connect) = self.on_connect { - Some(on_connect(&io)) - } else { - None - }; - - H2ServiceHandlerResponse { - state: State::Handshake( - Some(self.srv.clone()), - Some(self.cfg.clone()), - addr, - on_connect, - server::handshake(io), - ), - } - } -} - -enum State, B: MessageBody> -where - T: AsyncRead + AsyncWrite + Unpin, - S::Future: 'static, -{ - Incoming(Dispatcher), - Handshake( - Option>, - Option, - Option, - Option>, - Handshake, - ), -} - -pub struct H2ServiceHandlerResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, -{ - state: State, -} - -impl Future for H2ServiceHandlerResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody, -{ - type Output = Result<(), DispatchError>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.state { - State::Incoming(ref mut disp) => Pin::new(disp).poll(cx), - State::Handshake( - ref mut srv, - ref mut config, - ref peer_addr, - ref mut on_connect, - ref mut handshake, - ) => match Pin::new(handshake).poll(cx) { - Poll::Ready(Ok(conn)) => { - self.state = State::Incoming(Dispatcher::new( - srv.take().unwrap(), - conn, - on_connect.take(), - config.take().unwrap(), - None, - *peer_addr, - )); - self.poll(cx) - } - Poll::Ready(Err(err)) => { - trace!("H2 handshake error: {}", err); - Poll::Ready(Err(err.into())) - } - Poll::Pending => Poll::Pending, - }, - } - } -} diff --git a/actix-http/src/header/common/accept.rs b/actix-http/src/header/common/accept.rs deleted file mode 100644 index d52eba241..000000000 --- a/actix-http/src/header/common/accept.rs +++ /dev/null @@ -1,160 +0,0 @@ -use mime::Mime; - -use crate::header::{qitem, QualityItem}; -use crate::http::header; - -header! { - /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) - /// - /// The `Accept` header field can be used by user agents to specify - /// response media types that are acceptable. Accept header fields can - /// be used to indicate that the request is specifically limited to a - /// small set of desired types, as in the case of a request for an - /// in-line image - /// - /// # ABNF - /// - /// ```text - /// Accept = #( media-range [ accept-params ] ) - /// - /// media-range = ( "*/*" - /// / ( type "/" "*" ) - /// / ( type "/" subtype ) - /// ) *( OWS ";" OWS parameter ) - /// accept-params = weight *( accept-ext ) - /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] - /// ``` - /// - /// # Example values - /// * `audio/*; q=0.2, audio/basic` - /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` - /// - /// # Examples - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; - /// use actix_http::Response; - /// use actix_http::http::header::{Accept, qitem}; - /// - /// # fn main() { - /// let mut builder = Response::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; - /// use actix_http::Response; - /// use actix_http::http::header::{Accept, qitem}; - /// - /// # fn main() { - /// let mut builder = Response::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::APPLICATION_JSON), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; - /// use actix_http::Response; - /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; - /// - /// # fn main() { - /// let mut builder = Response::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// qitem("application/xhtml+xml".parse().unwrap()), - /// QualityItem::new( - /// mime::TEXT_XML, - /// q(900) - /// ), - /// qitem("image/webp".parse().unwrap()), - /// QualityItem::new( - /// mime::STAR_STAR, - /// q(800) - /// ), - /// ]) - /// ); - /// # } - /// ``` - (Accept, header::ACCEPT) => (QualityItem)+ - - test_accept { - // Tests from the RFC - test_header!( - test1, - vec![b"audio/*; q=0.2, audio/basic"], - Some(HeaderField(vec![ - QualityItem::new("audio/*".parse().unwrap(), q(200)), - qitem("audio/basic".parse().unwrap()), - ]))); - test_header!( - test2, - vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], - Some(HeaderField(vec![ - QualityItem::new(mime::TEXT_PLAIN, q(500)), - qitem(mime::TEXT_HTML), - QualityItem::new( - "text/x-dvi".parse().unwrap(), - q(800)), - qitem("text/x-c".parse().unwrap()), - ]))); - // Custom tests - test_header!( - test3, - vec![b"text/plain; charset=utf-8"], - Some(Accept(vec![ - qitem(mime::TEXT_PLAIN_UTF_8), - ]))); - test_header!( - test4, - vec![b"text/plain; charset=utf-8; q=0.5"], - Some(Accept(vec![ - QualityItem::new(mime::TEXT_PLAIN_UTF_8, - q(500)), - ]))); - - #[test] - fn test_fuzzing1() { - use crate::test::TestRequest; - let req = TestRequest::with_header(crate::header::ACCEPT, "chunk#;e").finish(); - let header = Accept::parse(&req); - assert!(header.is_ok()); - } - } -} - -impl Accept { - /// A constructor to easily create `Accept: */*`. - pub fn star() -> Accept { - Accept(vec![qitem(mime::STAR_STAR)]) - } - - /// A constructor to easily create `Accept: application/json`. - pub fn json() -> Accept { - Accept(vec![qitem(mime::APPLICATION_JSON)]) - } - - /// A constructor to easily create `Accept: text/*`. - pub fn text() -> Accept { - Accept(vec![qitem(mime::TEXT_STAR)]) - } - - /// A constructor to easily create `Accept: image/*`. - pub fn image() -> Accept { - Accept(vec![qitem(mime::IMAGE_STAR)]) - } -} diff --git a/actix-http/src/header/common/accept_charset.rs b/actix-http/src/header/common/accept_charset.rs deleted file mode 100644 index 117e2015d..000000000 --- a/actix-http/src/header/common/accept_charset.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::header::{Charset, QualityItem, ACCEPT_CHARSET}; - -header! { - /// `Accept-Charset` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) - /// - /// The `Accept-Charset` header field can be sent by a user agent to - /// indicate what charsets are acceptable in textual response content. - /// This field allows user agents capable of understanding more - /// comprehensive or special-purpose charsets to signal that capability - /// to an origin server that is capable of representing information in - /// those charsets. - /// - /// # ABNF - /// - /// ```text - /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) - /// ``` - /// - /// # Example values - /// * `iso-8859-5, unicode-1-1;q=0.8` - /// - /// # Examples - /// ```rust - /// # extern crate actix_http; - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; - /// - /// # fn main() { - /// let mut builder = Response::Ok(); - /// builder.set( - /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) - /// ); - /// # } - /// ``` - /// ```rust - /// # extern crate actix_http; - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; - /// - /// # fn main() { - /// let mut builder = Response::Ok(); - /// builder.set( - /// AcceptCharset(vec![ - /// QualityItem::new(Charset::Us_Ascii, q(900)), - /// QualityItem::new(Charset::Iso_8859_10, q(200)), - /// ]) - /// ); - /// # } - /// ``` - /// ```rust - /// # extern crate actix_http; - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; - /// - /// # fn main() { - /// let mut builder = Response::Ok(); - /// builder.set( - /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) - /// ); - /// # } - /// ``` - (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ - - test_accept_charset { - /// Test case from RFC - test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); - } -} diff --git a/actix-http/src/header/common/accept_encoding.rs b/actix-http/src/header/common/accept_encoding.rs deleted file mode 100644 index c90f529bc..000000000 --- a/actix-http/src/header/common/accept_encoding.rs +++ /dev/null @@ -1,72 +0,0 @@ -use header::{Encoding, QualityItem}; - -header! { - /// `Accept-Encoding` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4) - /// - /// The `Accept-Encoding` header field can be used by user agents to - /// indicate what response content-codings are - /// acceptable in the response. An `identity` token is used as a synonym - /// for "no encoding" in order to communicate when no encoding is - /// preferred. - /// - /// # ABNF - /// - /// ```text - /// Accept-Encoding = #( codings [ weight ] ) - /// codings = content-coding / "identity" / "*" - /// ``` - /// - /// # Example values - /// * `compress, gzip` - /// * `` - /// * `*` - /// * `compress;q=0.5, gzip;q=1` - /// * `gzip;q=1.0, identity; q=0.5, *;q=0` - /// - /// # Examples - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// qitem(Encoding::Gzip), - /// qitem(Encoding::Deflate), - /// ]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// QualityItem::new(Encoding::Gzip, q(600)), - /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), - /// ]) - /// ); - /// ``` - (AcceptEncoding, "Accept-Encoding") => (QualityItem)* - - test_accept_encoding { - // From the RFC - test_header!(test1, vec![b"compress, gzip"]); - test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); - test_header!(test3, vec![b"*"]); - // Note: Removed quality 1 from gzip - test_header!(test4, vec![b"compress;q=0.5, gzip"]); - // Note: Removed quality 1 from gzip - test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); - } -} diff --git a/actix-http/src/header/common/accept_language.rs b/actix-http/src/header/common/accept_language.rs deleted file mode 100644 index 55879b57f..000000000 --- a/actix-http/src/header/common/accept_language.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::header::{QualityItem, ACCEPT_LANGUAGE}; -use language_tags::LanguageTag; - -header! { - /// `Accept-Language` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) - /// - /// The `Accept-Language` header field can be used by user agents to - /// indicate the set of natural languages that are preferred in the - /// response. - /// - /// # ABNF - /// - /// ```text - /// Accept-Language = 1#( language-range [ weight ] ) - /// language-range = - /// ``` - /// - /// # Example values - /// * `da, en-gb;q=0.8, en;q=0.7` - /// * `en-us;q=1.0, en;q=0.5, fr` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_http; - /// # extern crate language_tags; - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; - /// - /// # fn main() { - /// let mut builder = Response::Ok(); - /// let mut langtag: LanguageTag = Default::default(); - /// langtag.language = Some("en".to_owned()); - /// langtag.region = Some("US".to_owned()); - /// builder.set( - /// AcceptLanguage(vec![ - /// qitem(langtag), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; - /// use actix_http::Response; - /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; - /// # - /// # fn main() { - /// let mut builder = Response::Ok(); - /// builder.set( - /// AcceptLanguage(vec![ - /// qitem(langtag!(da)), - /// QualityItem::new(langtag!(en;;;GB), q(800)), - /// QualityItem::new(langtag!(en), q(700)), - /// ]) - /// ); - /// # } - /// ``` - (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ - - test_accept_language { - // From the RFC - test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); - // Own test - test_header!( - test2, vec![b"en-US, en; q=0.5, fr"], - Some(AcceptLanguage(vec![ - qitem("en-US".parse().unwrap()), - QualityItem::new("en".parse().unwrap(), q(500)), - qitem("fr".parse().unwrap()), - ]))); - } -} diff --git a/actix-http/src/header/common/allow.rs b/actix-http/src/header/common/allow.rs deleted file mode 100644 index 432cc00d5..000000000 --- a/actix-http/src/header/common/allow.rs +++ /dev/null @@ -1,85 +0,0 @@ -use http::Method; -use http::header; - -header! { - /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) - /// - /// The `Allow` header field lists the set of methods advertised as - /// supported by the target resource. The purpose of this field is - /// strictly to inform the recipient of valid request methods associated - /// with the resource. - /// - /// # ABNF - /// - /// ```text - /// Allow = #method - /// ``` - /// - /// # Example values - /// * `GET, HEAD, PUT` - /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` - /// * `` - /// - /// # Examples - /// - /// ```rust - /// # extern crate http; - /// # extern crate actix_http; - /// use actix_http::Response; - /// use actix_http::http::header::Allow; - /// use http::Method; - /// - /// # fn main() { - /// let mut builder = Response::Ok(); - /// builder.set( - /// Allow(vec![Method::GET]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate http; - /// # extern crate actix_http; - /// use actix_http::Response; - /// use actix_http::http::header::Allow; - /// use http::Method; - /// - /// # fn main() { - /// let mut builder = Response::Ok(); - /// builder.set( - /// Allow(vec![ - /// Method::GET, - /// Method::POST, - /// Method::PATCH, - /// ]) - /// ); - /// # } - /// ``` - (Allow, header::ALLOW) => (Method)* - - test_allow { - // From the RFC - test_header!( - test1, - vec![b"GET, HEAD, PUT"], - Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); - // Own tests - test_header!( - test2, - vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], - Some(HeaderField(vec![ - Method::OPTIONS, - Method::GET, - Method::PUT, - Method::POST, - Method::DELETE, - Method::HEAD, - Method::TRACE, - Method::CONNECT, - Method::PATCH]))); - test_header!( - test3, - vec![b""], - Some(HeaderField(Vec::::new()))); - } -} diff --git a/actix-http/src/header/common/cache_control.rs b/actix-http/src/header/common/cache_control.rs deleted file mode 100644 index ec94ce4a9..000000000 --- a/actix-http/src/header/common/cache_control.rs +++ /dev/null @@ -1,257 +0,0 @@ -use std::fmt::{self, Write}; -use std::str::FromStr; - -use http::header; - -use crate::header::{ - fmt_comma_delimited, from_comma_delimited, Header, IntoHeaderValue, Writer, -}; - -/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) -/// -/// The `Cache-Control` header field is used to specify directives for -/// caches along the request/response chain. Such cache directives are -/// unidirectional in that the presence of a directive in a request does -/// not imply that the same directive is to be given in the response. -/// -/// # ABNF -/// -/// ```text -/// Cache-Control = 1#cache-directive -/// cache-directive = token [ "=" ( token / quoted-string ) ] -/// ``` -/// -/// # Example values -/// -/// * `no-cache` -/// * `private, community="UCI"` -/// * `max-age=30` -/// -/// # Examples -/// ```rust -/// use actix_http::Response; -/// use actix_http::http::header::{CacheControl, CacheDirective}; -/// -/// let mut builder = Response::Ok(); -/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); -/// ``` -/// -/// ```rust -/// use actix_http::Response; -/// use actix_http::http::header::{CacheControl, CacheDirective}; -/// -/// let mut builder = Response::Ok(); -/// builder.set(CacheControl(vec![ -/// CacheDirective::NoCache, -/// CacheDirective::Private, -/// CacheDirective::MaxAge(360u32), -/// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), -/// ])); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub struct CacheControl(pub Vec); - -__hyper__deref!(CacheControl => Vec); - -//TODO: this could just be the header! macro -impl Header for CacheControl { - fn name() -> header::HeaderName { - header::CACHE_CONTROL - } - - #[inline] - fn parse(msg: &T) -> Result - where - T: crate::HttpMessage, - { - let directives = from_comma_delimited(msg.headers().get_all(&Self::name()))?; - if !directives.is_empty() { - Ok(CacheControl(directives)) - } else { - Err(crate::error::ParseError::Header) - } - } -} - -impl fmt::Display for CacheControl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt_comma_delimited(f, &self[..]) - } -} - -impl IntoHeaderValue for CacheControl { - type Error = header::InvalidHeaderValue; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - header::HeaderValue::from_maybe_shared(writer.take()) - } -} - -/// `CacheControl` contains a list of these directives. -#[derive(PartialEq, Clone, Debug)] -pub enum CacheDirective { - /// "no-cache" - NoCache, - /// "no-store" - NoStore, - /// "no-transform" - NoTransform, - /// "only-if-cached" - OnlyIfCached, - - // request directives - /// "max-age=delta" - MaxAge(u32), - /// "max-stale=delta" - MaxStale(u32), - /// "min-fresh=delta" - MinFresh(u32), - - // response directives - /// "must-revalidate" - MustRevalidate, - /// "public" - Public, - /// "private" - Private, - /// "proxy-revalidate" - ProxyRevalidate, - /// "s-maxage=delta" - SMaxAge(u32), - - /// Extension directives. Optionally include an argument. - Extension(String, Option), -} - -impl fmt::Display for CacheDirective { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use self::CacheDirective::*; - fmt::Display::fmt( - match *self { - NoCache => "no-cache", - NoStore => "no-store", - NoTransform => "no-transform", - OnlyIfCached => "only-if-cached", - - MaxAge(secs) => return write!(f, "max-age={}", secs), - MaxStale(secs) => return write!(f, "max-stale={}", secs), - MinFresh(secs) => return write!(f, "min-fresh={}", secs), - - MustRevalidate => "must-revalidate", - Public => "public", - Private => "private", - ProxyRevalidate => "proxy-revalidate", - SMaxAge(secs) => return write!(f, "s-maxage={}", secs), - - Extension(ref name, None) => &name[..], - Extension(ref name, Some(ref arg)) => { - return write!(f, "{}={}", name, arg); - } - }, - f, - ) - } -} - -impl FromStr for CacheDirective { - type Err = Option<::Err>; - fn from_str(s: &str) -> Result::Err>> { - use self::CacheDirective::*; - match s { - "no-cache" => Ok(NoCache), - "no-store" => Ok(NoStore), - "no-transform" => Ok(NoTransform), - "only-if-cached" => Ok(OnlyIfCached), - "must-revalidate" => Ok(MustRevalidate), - "public" => Ok(Public), - "private" => Ok(Private), - "proxy-revalidate" => Ok(ProxyRevalidate), - "" => Err(None), - _ => match s.find('=') { - Some(idx) if idx + 1 < s.len() => { - match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { - ("max-age", secs) => secs.parse().map(MaxAge).map_err(Some), - ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), - ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), - ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), - (left, right) => { - Ok(Extension(left.to_owned(), Some(right.to_owned()))) - } - } - } - Some(_) => Err(None), - None => Ok(Extension(s.to_owned(), None)), - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::header::Header; - use crate::test::TestRequest; - - #[test] - fn test_parse_multiple_headers() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private") - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::NoCache, - CacheDirective::Private, - ])) - ) - } - - #[test] - fn test_parse_argument() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private") - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::MaxAge(100), - CacheDirective::Private, - ])) - ) - } - - #[test] - fn test_parse_quote_form() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![CacheDirective::MaxAge(200)])) - ) - } - - #[test] - fn test_parse_extension() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::Extension("foo".to_owned(), None), - CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())), - ])) - ) - } - - #[test] - fn test_parse_bad_syntax() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish(); - let cache: Result = Header::parse(&req); - assert_eq!(cache.ok(), None) - } -} diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs deleted file mode 100644 index d0d5af765..000000000 --- a/actix-http/src/header/common/content_disposition.rs +++ /dev/null @@ -1,998 +0,0 @@ -// # References -// -// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt -// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt -// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt -// Browser conformance tests at: http://greenbytes.de/tech/tc2231/ -// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml - -use lazy_static::lazy_static; -use regex::Regex; -use std::fmt::{self, Write}; - -use crate::header::{self, ExtendedValue, Header, IntoHeaderValue, Writer}; - -/// Split at the index of the first `needle` if it exists or at the end. -fn split_once(haystack: &str, needle: char) -> (&str, &str) { - haystack.find(needle).map_or_else( - || (haystack, ""), - |sc| { - let (first, last) = haystack.split_at(sc); - (first, last.split_at(1).1) - }, - ) -} - -/// Split at the index of the first `needle` if it exists or at the end, trim the right of the -/// first part and the left of the last part. -fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { - let (first, last) = split_once(haystack, needle); - (first.trim_end(), last.trim_start()) -} - -/// The implied disposition of the content of the HTTP body. -#[derive(Clone, Debug, PartialEq)] -pub enum DispositionType { - /// Inline implies default processing - Inline, - /// Attachment implies that the recipient should prompt the user to save the response locally, - /// rather than process it normally (as per its media type). - Attachment, - /// Used in *multipart/form-data* as defined in - /// [RFC7578](https://tools.ietf.org/html/rfc7578) to carry the field name and the file name. - FormData, - /// Extension type. Should be handled by recipients the same way as Attachment - Ext(String), -} - -impl<'a> From<&'a str> for DispositionType { - fn from(origin: &'a str) -> DispositionType { - if origin.eq_ignore_ascii_case("inline") { - DispositionType::Inline - } else if origin.eq_ignore_ascii_case("attachment") { - DispositionType::Attachment - } else if origin.eq_ignore_ascii_case("form-data") { - DispositionType::FormData - } else { - DispositionType::Ext(origin.to_owned()) - } - } -} - -/// Parameter in [`ContentDisposition`]. -/// -/// # Examples -/// ``` -/// use actix_http::http::header::DispositionParam; -/// -/// let param = DispositionParam::Filename(String::from("sample.txt")); -/// assert!(param.is_filename()); -/// assert_eq!(param.as_filename().unwrap(), "sample.txt"); -/// ``` -#[derive(Clone, Debug, PartialEq)] -#[allow(clippy::large_enum_variant)] -pub enum DispositionParam { - /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from - /// the form. - Name(String), - /// A plain file name. - /// - /// It is [not supposed](https://tools.ietf.org/html/rfc6266#appendix-D) to contain any - /// non-ASCII characters when used in a *Content-Disposition* HTTP response header, where - /// [`FilenameExt`](DispositionParam::FilenameExt) with charset UTF-8 may be used instead - /// in case there are Unicode characters in file names. - Filename(String), - /// An extended file name. It must not exist for `ContentType::Formdata` according to - /// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2). - FilenameExt(ExtendedValue), - /// An unrecognized regular parameter as defined in - /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in - /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should - /// ignore unrecognizable parameters. - Unknown(String, String), - /// An unrecognized extended paramater as defined in - /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in - /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single - /// trailling asterisk is not included. Recipients should ignore unrecognizable parameters. - UnknownExt(String, ExtendedValue), -} - -impl DispositionParam { - /// Returns `true` if the paramater is [`Name`](DispositionParam::Name). - #[inline] - pub fn is_name(&self) -> bool { - self.as_name().is_some() - } - - /// Returns `true` if the paramater is [`Filename`](DispositionParam::Filename). - #[inline] - pub fn is_filename(&self) -> bool { - self.as_filename().is_some() - } - - /// Returns `true` if the paramater is [`FilenameExt`](DispositionParam::FilenameExt). - #[inline] - pub fn is_filename_ext(&self) -> bool { - self.as_filename_ext().is_some() - } - - /// Returns `true` if the paramater is [`Unknown`](DispositionParam::Unknown) and the `name` - #[inline] - /// matches. - pub fn is_unknown>(&self, name: T) -> bool { - self.as_unknown(name).is_some() - } - - /// Returns `true` if the paramater is [`UnknownExt`](DispositionParam::UnknownExt) and the - /// `name` matches. - #[inline] - pub fn is_unknown_ext>(&self, name: T) -> bool { - self.as_unknown_ext(name).is_some() - } - - /// Returns the name if applicable. - #[inline] - pub fn as_name(&self) -> Option<&str> { - match self { - DispositionParam::Name(ref name) => Some(name.as_str()), - _ => None, - } - } - - /// Returns the filename if applicable. - #[inline] - pub fn as_filename(&self) -> Option<&str> { - match self { - DispositionParam::Filename(ref filename) => Some(filename.as_str()), - _ => None, - } - } - - /// Returns the filename* if applicable. - #[inline] - pub fn as_filename_ext(&self) -> Option<&ExtendedValue> { - match self { - DispositionParam::FilenameExt(ref value) => Some(value), - _ => None, - } - } - - /// Returns the value of the unrecognized regular parameter if it is - /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. - #[inline] - pub fn as_unknown>(&self, name: T) -> Option<&str> { - match self { - DispositionParam::Unknown(ref ext_name, ref value) - if ext_name.eq_ignore_ascii_case(name.as_ref()) => - { - Some(value.as_str()) - } - _ => None, - } - } - - /// Returns the value of the unrecognized extended parameter if it is - /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. - #[inline] - pub fn as_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { - match self { - DispositionParam::UnknownExt(ref ext_name, ref value) - if ext_name.eq_ignore_ascii_case(name.as_ref()) => - { - Some(value) - } - _ => None, - } - } -} - -/// A *Content-Disposition* header. It is compatible to be used either as -/// [a response header for the main body](https://mdn.io/Content-Disposition#As_a_response_header_for_the_main_body) -/// as (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266), or as -/// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body) -/// as (re)defined in [RFC7587](https://tools.ietf.org/html/rfc7578). -/// -/// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if -/// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as -/// part of a Web page, or as an attachment, that is downloaded and saved locally, and also can be -/// used to attach additional metadata, such as the filename to use when saving the response payload -/// locally. -/// -/// In a *multipart/form-data* body, the HTTP *Content-Disposition* general header is a header that -/// can be used on the subpart of a multipart body to give information about the field it applies to. -/// The subpart is delimited by the boundary defined in the *Content-Type* header. Used on the body -/// itself, *Content-Disposition* has no effect. -/// -/// # ABNF - -/// ```text -/// content-disposition = "Content-Disposition" ":" -/// disposition-type *( ";" disposition-parm ) -/// -/// disposition-type = "inline" | "attachment" | disp-ext-type -/// ; case-insensitive -/// -/// disp-ext-type = token -/// -/// disposition-parm = filename-parm | disp-ext-parm -/// -/// filename-parm = "filename" "=" value -/// | "filename*" "=" ext-value -/// -/// disp-ext-parm = token "=" value -/// | ext-token "=" ext-value -/// -/// ext-token = -/// ``` -/// -/// # Note -/// -/// filename is [not supposed](https://tools.ietf.org/html/rfc6266#appendix-D) to contain any -/// non-ASCII characters when used in a *Content-Disposition* HTTP response header, where -/// filename* with charset UTF-8 may be used instead in case there are Unicode characters in file -/// names. -/// filename is [acceptable](https://tools.ietf.org/html/rfc7578#section-4.2) to be UTF-8 encoded -/// directly in a *Content-Disposition* header for *multipart/form-data*, though. -/// -/// filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within -/// *multipart/form-data*. -/// -/// # Example -/// -/// ``` -/// use actix_http::http::header::{ -/// Charset, ContentDisposition, DispositionParam, DispositionType, -/// ExtendedValue, -/// }; -/// -/// let cd1 = ContentDisposition { -/// disposition: DispositionType::Attachment, -/// parameters: vec![DispositionParam::FilenameExt(ExtendedValue { -/// charset: Charset::Iso_8859_1, // The character set for the bytes of the filename -/// language_tag: None, // The optional language tag (see `language-tag` crate) -/// value: b"\xa9 Copyright 1989.txt".to_vec(), // the actual bytes of the filename -/// })], -/// }; -/// assert!(cd1.is_attachment()); -/// assert!(cd1.get_filename_ext().is_some()); -/// -/// let cd2 = ContentDisposition { -/// disposition: DispositionType::FormData, -/// parameters: vec![ -/// DispositionParam::Name(String::from("file")), -/// DispositionParam::Filename(String::from("bill.odt")), -/// ], -/// }; -/// assert_eq!(cd2.get_name(), Some("file")); // field name -/// assert_eq!(cd2.get_filename(), Some("bill.odt")); -/// -/// // HTTP response header with Unicode characters in file names -/// let cd3 = ContentDisposition { -/// disposition: DispositionType::Attachment, -/// parameters: vec![ -/// DispositionParam::FilenameExt(ExtendedValue { -/// charset: Charset::Ext(String::from("UTF-8")), -/// language_tag: None, -/// value: String::from("\u{1f600}.svg").into_bytes(), -/// }), -/// // fallback for better compatibility -/// DispositionParam::Filename(String::from("Grinning-Face-Emoji.svg")) -/// ], -/// }; -/// assert_eq!(cd3.get_filename_ext().map(|ev| ev.value.as_ref()), -/// Some("\u{1f600}.svg".as_bytes())); -/// ``` -/// -/// # WARN -/// If "filename" parameter is supplied, do not use the file name blindly, check and possibly -/// change to match local file system conventions if applicable, and do not use directory path -/// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3) -/// . -#[derive(Clone, Debug, PartialEq)] -pub struct ContentDisposition { - /// The disposition type - pub disposition: DispositionType, - /// Disposition parameters - pub parameters: Vec, -} - -impl ContentDisposition { - /// Parse a raw Content-Disposition header value. - pub fn from_raw(hv: &header::HeaderValue) -> Result { - // `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible - // ASCII characters. So `hv.as_bytes` is necessary here. - let hv = String::from_utf8(hv.as_bytes().to_vec()) - .map_err(|_| crate::error::ParseError::Header)?; - let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';'); - if disp_type.is_empty() { - return Err(crate::error::ParseError::Header); - } - let mut cd = ContentDisposition { - disposition: disp_type.into(), - parameters: Vec::new(), - }; - - while !left.is_empty() { - let (param_name, new_left) = split_once_and_trim(left, '='); - if param_name.is_empty() || param_name == "*" || new_left.is_empty() { - return Err(crate::error::ParseError::Header); - } - left = new_left; - if param_name.ends_with('*') { - // extended parameters - let param_name = ¶m_name[..param_name.len() - 1]; // trim asterisk - let (ext_value, new_left) = split_once_and_trim(left, ';'); - left = new_left; - let ext_value = header::parse_extended_value(ext_value)?; - - let param = if param_name.eq_ignore_ascii_case("filename") { - DispositionParam::FilenameExt(ext_value) - } else { - DispositionParam::UnknownExt(param_name.to_owned(), ext_value) - }; - cd.parameters.push(param); - } else { - // regular parameters - let value = if left.starts_with('\"') { - // quoted-string: defined in RFC6266 -> RFC2616 Section 3.6 - let mut escaping = false; - let mut quoted_string = vec![]; - let mut end = None; - // search for closing quote - for (i, &c) in left.as_bytes().iter().skip(1).enumerate() { - if escaping { - escaping = false; - quoted_string.push(c); - } else if c == 0x5c { - // backslash - escaping = true; - } else if c == 0x22 { - // double quote - end = Some(i + 1); // cuz skipped 1 for the leading quote - break; - } else { - quoted_string.push(c); - } - } - left = &left[end.ok_or(crate::error::ParseError::Header)? + 1..]; - left = split_once(left, ';').1.trim_start(); - // In fact, it should not be Err if the above code is correct. - String::from_utf8(quoted_string) - .map_err(|_| crate::error::ParseError::Header)? - } else { - // token: won't contains semicolon according to RFC 2616 Section 2.2 - let (token, new_left) = split_once_and_trim(left, ';'); - left = new_left; - if token.is_empty() { - // quoted-string can be empty, but token cannot be empty - return Err(crate::error::ParseError::Header); - } - token.to_owned() - }; - - let param = if param_name.eq_ignore_ascii_case("name") { - DispositionParam::Name(value) - } else if param_name.eq_ignore_ascii_case("filename") { - // See also comments in test_from_raw_uncessary_percent_decode. - DispositionParam::Filename(value) - } else { - DispositionParam::Unknown(param_name.to_owned(), value) - }; - cd.parameters.push(param); - } - } - - Ok(cd) - } - - /// Returns `true` if it is [`Inline`](DispositionType::Inline). - pub fn is_inline(&self) -> bool { - match self.disposition { - DispositionType::Inline => true, - _ => false, - } - } - - /// Returns `true` if it is [`Attachment`](DispositionType::Attachment). - pub fn is_attachment(&self) -> bool { - match self.disposition { - DispositionType::Attachment => true, - _ => false, - } - } - - /// Returns `true` if it is [`FormData`](DispositionType::FormData). - pub fn is_form_data(&self) -> bool { - match self.disposition { - DispositionType::FormData => true, - _ => false, - } - } - - /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. - pub fn is_ext>(&self, disp_type: T) -> bool { - match self.disposition { - DispositionType::Ext(ref t) - if t.eq_ignore_ascii_case(disp_type.as_ref()) => - { - true - } - _ => false, - } - } - - /// Return the value of *name* if exists. - pub fn get_name(&self) -> Option<&str> { - self.parameters.iter().filter_map(|p| p.as_name()).nth(0) - } - - /// Return the value of *filename* if exists. - pub fn get_filename(&self) -> Option<&str> { - self.parameters - .iter() - .filter_map(|p| p.as_filename()) - .nth(0) - } - - /// Return the value of *filename\** if exists. - pub fn get_filename_ext(&self) -> Option<&ExtendedValue> { - self.parameters - .iter() - .filter_map(|p| p.as_filename_ext()) - .nth(0) - } - - /// Return the value of the parameter which the `name` matches. - pub fn get_unknown>(&self, name: T) -> Option<&str> { - let name = name.as_ref(); - self.parameters - .iter() - .filter_map(|p| p.as_unknown(name)) - .nth(0) - } - - /// Return the value of the extended parameter which the `name` matches. - pub fn get_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { - let name = name.as_ref(); - self.parameters - .iter() - .filter_map(|p| p.as_unknown_ext(name)) - .nth(0) - } -} - -impl IntoHeaderValue for ContentDisposition { - type Error = header::InvalidHeaderValue; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - header::HeaderValue::from_maybe_shared(writer.take()) - } -} - -impl Header for ContentDisposition { - fn name() -> header::HeaderName { - header::CONTENT_DISPOSITION - } - - fn parse(msg: &T) -> Result { - if let Some(h) = msg.headers().get(&Self::name()) { - Self::from_raw(&h) - } else { - Err(crate::error::ParseError::Header) - } - } -} - -impl fmt::Display for DispositionType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - DispositionType::Inline => write!(f, "inline"), - DispositionType::Attachment => write!(f, "attachment"), - DispositionType::FormData => write!(f, "form-data"), - DispositionType::Ext(ref s) => write!(f, "{}", s), - } - } -} - -impl fmt::Display for DispositionParam { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // All ASCII control characters (0-30, 127) including horizontal tab, double quote, and - // backslash should be escaped in quoted-string (i.e. "foobar"). - // Ref: RFC6266 S4.1 -> RFC2616 S3.6 - // filename-parm = "filename" "=" value - // value = token | quoted-string - // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) - // qdtext = > - // quoted-pair = "\" CHAR - // TEXT = - // LWS = [CRLF] 1*( SP | HT ) - // OCTET = - // CHAR = - // CTL = - // - // Ref: RFC7578 S4.2 -> RFC2183 S2 -> RFC2045 S5.1 - // parameter := attribute "=" value - // attribute := token - // ; Matching of attributes - // ; is ALWAYS case-insensitive. - // value := token / quoted-string - // token := 1* - // tspecials := "(" / ")" / "<" / ">" / "@" / - // "," / ";" / ":" / "\" / <"> - // "/" / "[" / "]" / "?" / "=" - // ; Must be in quoted-string, - // ; to use within parameter values - // - // - // See also comments in test_from_raw_uncessary_percent_decode. - lazy_static! { - static ref RE: Regex = Regex::new("[\x00-\x08\x10-\x1F\x7F\"\\\\]").unwrap(); - } - match self { - DispositionParam::Name(ref value) => write!(f, "name={}", value), - DispositionParam::Filename(ref value) => { - write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref()) - } - DispositionParam::Unknown(ref name, ref value) => write!( - f, - "{}=\"{}\"", - name, - &RE.replace_all(value, "\\$0").as_ref() - ), - DispositionParam::FilenameExt(ref ext_value) => { - write!(f, "filename*={}", ext_value) - } - DispositionParam::UnknownExt(ref name, ref ext_value) => { - write!(f, "{}*={}", name, ext_value) - } - } - } -} - -impl fmt::Display for ContentDisposition { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.disposition)?; - self.parameters - .iter() - .map(|param| write!(f, "; {}", param)) - .collect() - } -} - -#[cfg(test)] -mod tests { - use super::{ContentDisposition, DispositionParam, DispositionType}; - use crate::header::shared::Charset; - use crate::header::{ExtendedValue, HeaderValue}; - - #[test] - fn test_from_raw_basic() { - assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); - - let a = HeaderValue::from_static( - "form-data; dummy=3; name=upload; filename=\"sample.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static("attachment; filename=\"image.jpg\""); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static("inline; filename=image.jpg"); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Unknown( - String::from("creation-date"), - "Wed, 12 Feb 1997 16:29:51 -0500".to_owned(), - )], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_extended() { - let a = HeaderValue::from_static( - "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Ext(String::from("UTF-8")), - language_tag: None, - value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', - ], - })], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Ext(String::from("UTF-8")), - language_tag: None, - value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', - ], - })], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_extra_whitespace() { - let a = HeaderValue::from_static( - "form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_unordered() { - let a = HeaderValue::from_static( - "form-data; dummy=3; filename=\"sample.png\" ; name=upload;", - // Actually, a trailling semolocon is not compliant. But it is fine to accept. - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - DispositionParam::Name("upload".to_owned()), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_str( - "attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"", - ) - .unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![ - DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: None, - value: b"foo-\xe4.html".to_vec(), - }), - DispositionParam::Filename("foo-ä.html".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_only_disp() { - let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")) - .unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![], - }; - assert_eq!(a, b); - - let a = - ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![], - }; - assert_eq!(a, b); - - let a = ContentDisposition::from_raw(&HeaderValue::from_static( - "unknown-disp-param", - )) - .unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Ext(String::from("unknown-disp-param")), - parameters: vec![], - }; - assert_eq!(a, b); - } - - #[test] - fn from_raw_with_mixed_case() { - let a = HeaderValue::from_str( - "InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"", - ) - .unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![ - DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: None, - value: b"foo-\xe4.html".to_vec(), - }), - DispositionParam::Filename("foo-ä.html".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn from_raw_with_unicode() { - /* RFC7578 Section 4.2: - Some commonly deployed systems use multipart/form-data with file names directly encoded - including octets outside the US-ASCII range. The encoding used for the file names is - typically UTF-8, although HTML forms will use the charset associated with the form. - - Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above. - (And now, only UTF-8 is handled by this implementation.) - */ - let a = HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"") - .unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name(String::from("upload")), - DispositionParam::Filename(String::from("文件.webp")), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_str( - "form-data; name=upload; filename=\"余固知謇謇之為患兮,å¿è€Œä¸èƒ½èˆä¹Ÿ.pptx\"", - ) - .unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name(String::from("upload")), - DispositionParam::Filename(String::from( - "余固知謇謇之為患兮,å¿è€Œä¸èƒ½èˆä¹Ÿ.pptx", - )), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_escape() { - let a = HeaderValue::from_static( - "form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename( - ['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g'] - .iter() - .collect(), - ), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_semicolon() { - let a = - HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\""); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![DispositionParam::Filename(String::from( - "A semicolon here;.pdf", - ))], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_uncessary_percent_decode() { - // In fact, RFC7578 (multipart/form-data) Section 2 and 4.2 suggests that filename with - // non-ASCII characters MAY be percent-encoded. - // On the contrary, RFC6266 or other RFCs related to Content-Disposition response header - // do not mention such percent-encoding. - // So, it appears to be undecidable whether to percent-decode or not without - // knowing the usage scenario (multipart/form-data v.s. HTTP response header) and - // inevitable to unnecessarily percent-decode filename with %XX in the former scenario. - // Fortunately, it seems that almost all mainstream browsers just send UTF-8 encoded file - // names in quoted-string format (tested on Edge, IE11, Chrome and Firefox) without - // percent-encoding. So we do not bother to attempt to percent-decode. - let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name("photo".to_owned()), - DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name("photo".to_owned()), - DispositionParam::Filename(String::from("%74%65%73%74.png")), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_param_value_missing() { - let a = HeaderValue::from_static("form-data; name=upload ; filename="); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf"); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; filename= "); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; filename=\"\""); - assert!(ContentDisposition::from_raw(&a) - .expect("parse cd") - .get_filename() - .expect("filename") - .is_empty()); - } - - #[test] - fn test_from_raw_param_name_missing() { - let a = HeaderValue::from_static("inline; =\"test.txt\""); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; =diary.odt"); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; ="); - assert!(ContentDisposition::from_raw(&a).is_err()); - } - - #[test] - fn test_display_extended() { - let as_string = - "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - - let a = HeaderValue::from_static("attachment; filename=colourful.csv"); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!( - "attachment; filename=\"colourful.csv\"".to_owned(), - display_rendered - ); - } - - #[test] - fn test_display_quote() { - let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\""; - as_string - .find(['\\', '\"'].iter().collect::().as_str()) - .unwrap(); // ensure `\"` is there - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - } - - #[test] - fn test_display_space_tab() { - let as_string = "form-data; name=upload; filename=\"Space here.png\""; - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - - let a: ContentDisposition = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))], - }; - let display_rendered = format!("{}", a); - assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered); - } - - #[test] - fn test_display_control_characters() { - /* let a = "attachment; filename=\"carriage\rreturn.png\""; - let a = HeaderValue::from_static(a); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!( - "attachment; filename=\"carriage\\\rreturn.png\"", - display_rendered - );*/ - // No way to create a HeaderValue containing a carriage return. - - let a: ContentDisposition = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))], - }; - let display_rendered = format!("{}", a); - assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered); - } - - #[test] - fn test_param_methods() { - let param = DispositionParam::Filename(String::from("sample.txt")); - assert!(param.is_filename()); - assert_eq!(param.as_filename().unwrap(), "sample.txt"); - - let param = DispositionParam::Unknown(String::from("foo"), String::from("bar")); - assert!(param.is_unknown("foo")); - assert_eq!(param.as_unknown("fOo"), Some("bar")); - } - - #[test] - fn test_disposition_methods() { - let cd = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(cd.get_name(), Some("upload")); - assert_eq!(cd.get_unknown("dummy"), Some("3")); - assert_eq!(cd.get_filename(), Some("sample.png")); - assert_eq!(cd.get_unknown_ext("dummy"), None); - assert_eq!(cd.get_unknown("duMMy"), Some("3")); - } -} diff --git a/actix-http/src/header/common/content_language.rs b/actix-http/src/header/common/content_language.rs deleted file mode 100644 index 838981a39..000000000 --- a/actix-http/src/header/common/content_language.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::header::{QualityItem, CONTENT_LANGUAGE}; -use language_tags::LanguageTag; - -header! { - /// `Content-Language` header, defined in - /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) - /// - /// The `Content-Language` header field describes the natural language(s) - /// of the intended audience for the representation. Note that this - /// might not be equivalent to all the languages used within the - /// representation. - /// - /// # ABNF - /// - /// ```text - /// Content-Language = 1#language-tag - /// ``` - /// - /// # Example values - /// - /// * `da` - /// * `mi, en` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; - /// use actix_http::Response; - /// # use actix_http::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { - /// let mut builder = Response::Ok(); - /// builder.set( - /// ContentLanguage(vec![ - /// qitem(langtag!(en)), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; - /// use actix_http::Response; - /// # use actix_http::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { - /// - /// let mut builder = Response::Ok(); - /// builder.set( - /// ContentLanguage(vec![ - /// qitem(langtag!(da)), - /// qitem(langtag!(en;;;GB)), - /// ]) - /// ); - /// # } - /// ``` - (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ - - test_content_language { - test_header!(test1, vec![b"da"]); - test_header!(test2, vec![b"mi, en"]); - } -} diff --git a/actix-http/src/header/common/content_range.rs b/actix-http/src/header/common/content_range.rs deleted file mode 100644 index 9a604c641..000000000 --- a/actix-http/src/header/common/content_range.rs +++ /dev/null @@ -1,208 +0,0 @@ -use std::fmt::{self, Display, Write}; -use std::str::FromStr; - -use crate::error::ParseError; -use crate::header::{ - HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE, -}; - -header! { - /// `Content-Range` header, defined in - /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) - (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] - - test_content_range { - test_header!(test_bytes, - vec![b"bytes 0-499/500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - instance_length: Some(500) - }))); - - test_header!(test_bytes_unknown_len, - vec![b"bytes 0-499/*"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - instance_length: None - }))); - - test_header!(test_bytes_unknown_range, - vec![b"bytes */500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: None, - instance_length: Some(500) - }))); - - test_header!(test_unregistered, - vec![b"seconds 1-2"], - Some(ContentRange(ContentRangeSpec::Unregistered { - unit: "seconds".to_owned(), - resp: "1-2".to_owned() - }))); - - test_header!(test_no_len, - vec![b"bytes 0-499"], - None::); - - test_header!(test_only_unit, - vec![b"bytes"], - None::); - - test_header!(test_end_less_than_start, - vec![b"bytes 499-0/500"], - None::); - - test_header!(test_blank, - vec![b""], - None::); - - test_header!(test_bytes_many_spaces, - vec![b"bytes 1-2/500 3"], - None::); - - test_header!(test_bytes_many_slashes, - vec![b"bytes 1-2/500/600"], - None::); - - test_header!(test_bytes_many_dashes, - vec![b"bytes 1-2-3/500"], - None::); - - } -} - -/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) -/// -/// # ABNF -/// -/// ```text -/// Content-Range = byte-content-range -/// / other-content-range -/// -/// byte-content-range = bytes-unit SP -/// ( byte-range-resp / unsatisfied-range ) -/// -/// byte-range-resp = byte-range "/" ( complete-length / "*" ) -/// byte-range = first-byte-pos "-" last-byte-pos -/// unsatisfied-range = "*/" complete-length -/// -/// complete-length = 1*DIGIT -/// -/// other-content-range = other-range-unit SP other-range-resp -/// other-range-resp = *CHAR -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub enum ContentRangeSpec { - /// Byte range - Bytes { - /// First and last bytes of the range, omitted if request could not be - /// satisfied - range: Option<(u64, u64)>, - - /// Total length of the instance, can be omitted if unknown - instance_length: Option, - }, - - /// Custom range, with unit not registered at IANA - Unregistered { - /// other-range-unit - unit: String, - - /// other-range-resp - resp: String, - }, -} - -fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { - let mut iter = s.splitn(2, separator); - match (iter.next(), iter.next()) { - (Some(a), Some(b)) => Some((a, b)), - _ => None, - } -} - -impl FromStr for ContentRangeSpec { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - let res = match split_in_two(s, ' ') { - Some(("bytes", resp)) => { - let (range, instance_length) = - split_in_two(resp, '/').ok_or(ParseError::Header)?; - - let instance_length = if instance_length == "*" { - None - } else { - Some(instance_length.parse().map_err(|_| ParseError::Header)?) - }; - - let range = if range == "*" { - None - } else { - let (first_byte, last_byte) = - split_in_two(range, '-').ok_or(ParseError::Header)?; - let first_byte = - first_byte.parse().map_err(|_| ParseError::Header)?; - let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?; - if last_byte < first_byte { - return Err(ParseError::Header); - } - Some((first_byte, last_byte)) - }; - - ContentRangeSpec::Bytes { - range, - instance_length, - } - } - Some((unit, resp)) => ContentRangeSpec::Unregistered { - unit: unit.to_owned(), - resp: resp.to_owned(), - }, - _ => return Err(ParseError::Header), - }; - Ok(res) - } -} - -impl Display for ContentRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - ContentRangeSpec::Bytes { - range, - instance_length, - } => { - f.write_str("bytes ")?; - match range { - Some((first_byte, last_byte)) => { - write!(f, "{}-{}", first_byte, last_byte)?; - } - None => { - f.write_str("*")?; - } - }; - f.write_str("/")?; - if let Some(v) = instance_length { - write!(f, "{}", v) - } else { - f.write_str("*") - } - } - ContentRangeSpec::Unregistered { ref unit, ref resp } => { - f.write_str(unit)?; - f.write_str(" ")?; - f.write_str(resp) - } - } - } -} - -impl IntoHeaderValue for ContentRangeSpec { - type Error = InvalidHeaderValue; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - HeaderValue::from_maybe_shared(writer.take()) - } -} diff --git a/actix-http/src/header/common/content_type.rs b/actix-http/src/header/common/content_type.rs deleted file mode 100644 index a0baa5637..000000000 --- a/actix-http/src/header/common/content_type.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::header::CONTENT_TYPE; -use mime::Mime; - -header! { - /// `Content-Type` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) - /// - /// The `Content-Type` header field indicates the media type of the - /// associated representation: either the representation enclosed in the - /// message payload or the selected representation, as determined by the - /// message semantics. The indicated media type defines both the data - /// format and how that data is intended to be processed by a recipient, - /// within the scope of the received message semantics, after any content - /// codings indicated by Content-Encoding are decoded. - /// - /// Although the `mime` crate allows the mime options to be any slice, this crate - /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If - /// this is an issue, it's possible to implement `Header` on a custom struct. - /// - /// # ABNF - /// - /// ```text - /// Content-Type = media-type - /// ``` - /// - /// # Example values - /// - /// * `text/html; charset=utf-8` - /// * `application/json` - /// - /// # Examples - /// - /// ```rust - /// use actix_http::Response; - /// use actix_http::http::header::ContentType; - /// - /// # fn main() { - /// let mut builder = Response::Ok(); - /// builder.set( - /// ContentType::json() - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate mime; - /// # extern crate actix_http; - /// use mime::TEXT_HTML; - /// use actix_http::Response; - /// use actix_http::http::header::ContentType; - /// - /// # fn main() { - /// let mut builder = Response::Ok(); - /// builder.set( - /// ContentType(TEXT_HTML) - /// ); - /// # } - /// ``` - (ContentType, CONTENT_TYPE) => [Mime] - - test_content_type { - test_header!( - test1, - vec![b"text/html"], - Some(HeaderField(mime::TEXT_HTML))); - } -} - -impl ContentType { - /// A constructor to easily create a `Content-Type: application/json` - /// header. - #[inline] - pub fn json() -> ContentType { - ContentType(mime::APPLICATION_JSON) - } - - /// A constructor to easily create a `Content-Type: text/plain; - /// charset=utf-8` header. - #[inline] - pub fn plaintext() -> ContentType { - ContentType(mime::TEXT_PLAIN_UTF_8) - } - - /// A constructor to easily create a `Content-Type: text/html` header. - #[inline] - pub fn html() -> ContentType { - ContentType(mime::TEXT_HTML) - } - - /// A constructor to easily create a `Content-Type: text/xml` header. - #[inline] - pub fn xml() -> ContentType { - ContentType(mime::TEXT_XML) - } - - /// A constructor to easily create a `Content-Type: - /// application/www-form-url-encoded` header. - #[inline] - pub fn form_url_encoded() -> ContentType { - ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) - } - /// A constructor to easily create a `Content-Type: image/jpeg` header. - #[inline] - pub fn jpeg() -> ContentType { - ContentType(mime::IMAGE_JPEG) - } - - /// A constructor to easily create a `Content-Type: image/png` header. - #[inline] - pub fn png() -> ContentType { - ContentType(mime::IMAGE_PNG) - } - - /// A constructor to easily create a `Content-Type: - /// application/octet-stream` header. - #[inline] - pub fn octet_stream() -> ContentType { - ContentType(mime::APPLICATION_OCTET_STREAM) - } -} - -impl Eq for ContentType {} diff --git a/actix-http/src/header/common/date.rs b/actix-http/src/header/common/date.rs deleted file mode 100644 index 784100e8d..000000000 --- a/actix-http/src/header/common/date.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::header::{HttpDate, DATE}; -use std::time::SystemTime; - -header! { - /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) - /// - /// The `Date` header field represents the date and time at which the - /// message was originated. - /// - /// # ABNF - /// - /// ```text - /// Date = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Tue, 15 Nov 1994 08:12:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::Response; - /// use actix_http::http::header::Date; - /// use std::time::SystemTime; - /// - /// let mut builder = Response::Ok(); - /// builder.set(Date(SystemTime::now().into())); - /// ``` - (Date, DATE) => [HttpDate] - - test_date { - test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); - } -} - -impl Date { - /// Create a date instance set to the current system time - pub fn now() -> Date { - Date(SystemTime::now().into()) - } -} diff --git a/actix-http/src/header/common/etag.rs b/actix-http/src/header/common/etag.rs deleted file mode 100644 index 325b91cbf..000000000 --- a/actix-http/src/header/common/etag.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::header::{EntityTag, ETAG}; - -header! { - /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) - /// - /// The `ETag` header field in a response provides the current entity-tag - /// for the selected representation, as determined at the conclusion of - /// handling the request. An entity-tag is an opaque validator for - /// differentiating between multiple representations of the same - /// resource, regardless of whether those multiple representations are - /// due to resource state changes over time, content negotiation - /// resulting in multiple representations being valid at the same time, - /// or both. An entity-tag consists of an opaque quoted string, possibly - /// prefixed by a weakness indicator. - /// - /// # ABNF - /// - /// ```text - /// ETag = entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * `W/"xyzzy"` - /// * `""` - /// - /// # Examples - /// - /// ```rust - /// use actix_http::Response; - /// use actix_http::http::header::{ETag, EntityTag}; - /// - /// let mut builder = Response::Ok(); - /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); - /// ``` - /// - /// ```rust - /// use actix_http::Response; - /// use actix_http::http::header::{ETag, EntityTag}; - /// - /// let mut builder = Response::Ok(); - /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); - /// ``` - (ETag, ETAG) => [EntityTag] - - test_etag { - // From the RFC - test_header!(test1, - vec![b"\"xyzzy\""], - Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); - test_header!(test2, - vec![b"W/\"xyzzy\""], - Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); - test_header!(test3, - vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); - // Own tests - test_header!(test4, - vec![b"\"foobar\""], - Some(ETag(EntityTag::new(false, "foobar".to_owned())))); - test_header!(test5, - vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); - test_header!(test6, - vec![b"W/\"weak-etag\""], - Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); - test_header!(test7, - vec![b"W/\"\x65\x62\""], - Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); - test_header!(test8, - vec![b"W/\"\""], - Some(ETag(EntityTag::new(true, "".to_owned())))); - test_header!(test9, - vec![b"no-dquotes"], - None::); - test_header!(test10, - vec![b"w/\"the-first-w-is-case-sensitive\""], - None::); - test_header!(test11, - vec![b""], - None::); - test_header!(test12, - vec![b"\"unmatched-dquotes1"], - None::); - test_header!(test13, - vec![b"unmatched-dquotes2\""], - None::); - test_header!(test14, - vec![b"matched-\"dquotes\""], - None::); - test_header!(test15, - vec![b"\""], - None::); - } -} diff --git a/actix-http/src/header/common/expires.rs b/actix-http/src/header/common/expires.rs deleted file mode 100644 index 3b9a7873d..000000000 --- a/actix-http/src/header/common/expires.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::header::{HttpDate, EXPIRES}; - -header! { - /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) - /// - /// The `Expires` header field gives the date/time after which the - /// response is considered stale. - /// - /// The presence of an Expires field does not imply that the original - /// resource will change or cease to exist at, before, or after that - /// time. - /// - /// # ABNF - /// - /// ```text - /// Expires = HTTP-date - /// ``` - /// - /// # Example values - /// * `Thu, 01 Dec 1994 16:00:00 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::Response; - /// use actix_http::http::header::Expires; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = Response::Ok(); - /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); - /// builder.set(Expires(expiration.into())); - /// ``` - (Expires, EXPIRES) => [HttpDate] - - test_expires { - // Test case from RFC - test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); - } -} diff --git a/actix-http/src/header/common/if_match.rs b/actix-http/src/header/common/if_match.rs deleted file mode 100644 index 7e0e9a7e0..000000000 --- a/actix-http/src/header/common/if_match.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::header::{EntityTag, IF_MATCH}; - -header! { - /// `If-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) - /// - /// The `If-Match` header field makes the request method conditional on - /// the recipient origin server either having at least one current - /// representation of the target resource, when the field-value is "*", - /// or having a current representation of the target resource that has an - /// entity-tag matching a member of the list of entity-tags provided in - /// the field-value. - /// - /// An origin server MUST use the strong comparison function when - /// comparing entity-tags for `If-Match`, since the client - /// intends this precondition to prevent the method from being applied if - /// there have been any changes to the representation data. - /// - /// # ABNF - /// - /// ```text - /// If-Match = "*" / 1#entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * "xyzzy", "r2d2xxxx", "c3piozzzz" - /// - /// # Examples - /// - /// ```rust - /// use actix_http::Response; - /// use actix_http::http::header::IfMatch; - /// - /// let mut builder = Response::Ok(); - /// builder.set(IfMatch::Any); - /// ``` - /// - /// ```rust - /// use actix_http::Response; - /// use actix_http::http::header::{IfMatch, EntityTag}; - /// - /// let mut builder = Response::Ok(); - /// builder.set( - /// IfMatch::Items(vec![ - /// EntityTag::new(false, "xyzzy".to_owned()), - /// EntityTag::new(false, "foobar".to_owned()), - /// EntityTag::new(false, "bazquux".to_owned()), - /// ]) - /// ); - /// ``` - (IfMatch, IF_MATCH) => {Any / (EntityTag)+} - - test_if_match { - test_header!( - test1, - vec![b"\"xyzzy\""], - Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned())]))); - test_header!( - test2, - vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], - Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned()), - EntityTag::new(false, "r2d2xxxx".to_owned()), - EntityTag::new(false, "c3piozzzz".to_owned())]))); - test_header!(test3, vec![b"*"], Some(IfMatch::Any)); - } -} diff --git a/actix-http/src/header/common/if_modified_since.rs b/actix-http/src/header/common/if_modified_since.rs deleted file mode 100644 index 39aca595d..000000000 --- a/actix-http/src/header/common/if_modified_since.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::header::{HttpDate, IF_MODIFIED_SINCE}; - -header! { - /// `If-Modified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) - /// - /// The `If-Modified-Since` header field makes a GET or HEAD request - /// method conditional on the selected representation's modification date - /// being more recent than the date provided in the field-value. - /// Transfer of the selected representation's data is avoided if that - /// data has not changed. - /// - /// # ABNF - /// - /// ```text - /// If-Unmodified-Since = HTTP-date - /// ``` - /// - /// # Example values - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::Response; - /// use actix_http::http::header::IfModifiedSince; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = Response::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfModifiedSince(modified.into())); - /// ``` - (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate] - - test_if_modified_since { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } -} diff --git a/actix-http/src/header/common/if_none_match.rs b/actix-http/src/header/common/if_none_match.rs deleted file mode 100644 index 7f6ccb137..000000000 --- a/actix-http/src/header/common/if_none_match.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::header::{EntityTag, IF_NONE_MATCH}; - -header! { - /// `If-None-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) - /// - /// The `If-None-Match` header field makes the request method conditional - /// on a recipient cache or origin server either not having any current - /// representation of the target resource, when the field-value is "*", - /// or having a selected representation with an entity-tag that does not - /// match any of those listed in the field-value. - /// - /// A recipient MUST use the weak comparison function when comparing - /// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags - /// can be used for cache validation even if there have been changes to - /// the representation data. - /// - /// # ABNF - /// - /// ```text - /// If-None-Match = "*" / 1#entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * `W/"xyzzy"` - /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` - /// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` - /// * `*` - /// - /// # Examples - /// - /// ```rust - /// use actix_http::Response; - /// use actix_http::http::header::IfNoneMatch; - /// - /// let mut builder = Response::Ok(); - /// builder.set(IfNoneMatch::Any); - /// ``` - /// - /// ```rust - /// use actix_http::Response; - /// use actix_http::http::header::{IfNoneMatch, EntityTag}; - /// - /// let mut builder = Response::Ok(); - /// builder.set( - /// IfNoneMatch::Items(vec![ - /// EntityTag::new(false, "xyzzy".to_owned()), - /// EntityTag::new(false, "foobar".to_owned()), - /// EntityTag::new(false, "bazquux".to_owned()), - /// ]) - /// ); - /// ``` - (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} - - test_if_none_match { - test_header!(test1, vec![b"\"xyzzy\""]); - test_header!(test2, vec![b"W/\"xyzzy\""]); - test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); - test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); - test_header!(test5, vec![b"*"]); - } -} - -#[cfg(test)] -mod tests { - use super::IfNoneMatch; - use crate::header::{EntityTag, Header, IF_NONE_MATCH}; - use crate::test::TestRequest; - - #[test] - fn test_if_none_match() { - let mut if_none_match: Result; - - let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish(); - if_none_match = Header::parse(&req); - assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); - - let req = - TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]) - .finish(); - - if_none_match = Header::parse(&req); - let mut entities: Vec = Vec::new(); - let foobar_etag = EntityTag::new(false, "foobar".to_owned()); - let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); - entities.push(foobar_etag); - entities.push(weak_etag); - assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities))); - } -} diff --git a/actix-http/src/header/common/if_range.rs b/actix-http/src/header/common/if_range.rs deleted file mode 100644 index b14ad0391..000000000 --- a/actix-http/src/header/common/if_range.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::fmt::{self, Display, Write}; - -use crate::error::ParseError; -use crate::header::{ - self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, - IntoHeaderValue, InvalidHeaderValue, Writer, -}; -use crate::httpmessage::HttpMessage; - -/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) -/// -/// If a client has a partial copy of a representation and wishes to have -/// an up-to-date copy of the entire representation, it could use the -/// Range header field with a conditional GET (using either or both of -/// If-Unmodified-Since and If-Match.) However, if the precondition -/// fails because the representation has been modified, the client would -/// then have to make a second request to obtain the entire current -/// representation. -/// -/// The `If-Range` header field allows a client to \"short-circuit\" the -/// second request. Informally, its meaning is as follows: if the -/// representation is unchanged, send me the part(s) that I am requesting -/// in Range; otherwise, send me the entire representation. -/// -/// # ABNF -/// -/// ```text -/// If-Range = entity-tag / HTTP-date -/// ``` -/// -/// # Example values -/// -/// * `Sat, 29 Oct 1994 19:43:31 GMT` -/// * `\"xyzzy\"` -/// -/// # Examples -/// -/// ```rust -/// use actix_http::Response; -/// use actix_http::http::header::{EntityTag, IfRange}; -/// -/// let mut builder = Response::Ok(); -/// builder.set(IfRange::EntityTag(EntityTag::new( -/// false, -/// "xyzzy".to_owned(), -/// ))); -/// ``` -/// -/// ```rust -/// use actix_http::Response; -/// use actix_http::http::header::IfRange; -/// use std::time::{Duration, SystemTime}; -/// -/// let mut builder = Response::Ok(); -/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); -/// builder.set(IfRange::Date(fetched.into())); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub enum IfRange { - /// The entity-tag the client has of the resource - EntityTag(EntityTag), - /// The date when the client retrieved the resource - Date(HttpDate), -} - -impl Header for IfRange { - fn name() -> HeaderName { - header::IF_RANGE - } - #[inline] - fn parse(msg: &T) -> Result - where - T: HttpMessage, - { - let etag: Result = - from_one_raw_str(msg.headers().get(&header::IF_RANGE)); - if let Ok(etag) = etag { - return Ok(IfRange::EntityTag(etag)); - } - let date: Result = - from_one_raw_str(msg.headers().get(&header::IF_RANGE)); - if let Ok(date) = date { - return Ok(IfRange::Date(date)); - } - Err(ParseError::Header) - } -} - -impl Display for IfRange { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - IfRange::EntityTag(ref x) => Display::fmt(x, f), - IfRange::Date(ref x) => Display::fmt(x, f), - } - } -} - -impl IntoHeaderValue for IfRange { - type Error = InvalidHeaderValue; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - HeaderValue::from_maybe_shared(writer.take()) - } -} - -#[cfg(test)] -mod test_if_range { - use super::IfRange as HeaderField; - use crate::header::*; - use std::str; - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - test_header!(test2, vec![b"\"xyzzy\""]); - test_header!(test3, vec![b"this-is-invalid"], None::); -} diff --git a/actix-http/src/header/common/if_unmodified_since.rs b/actix-http/src/header/common/if_unmodified_since.rs deleted file mode 100644 index d6c099e64..000000000 --- a/actix-http/src/header/common/if_unmodified_since.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::header::{HttpDate, IF_UNMODIFIED_SINCE}; - -header! { - /// `If-Unmodified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) - /// - /// The `If-Unmodified-Since` header field makes the request method - /// conditional on the selected representation's last modification date - /// being earlier than or equal to the date provided in the field-value. - /// This field accomplishes the same purpose as If-Match for cases where - /// the user agent does not have an entity-tag for the representation. - /// - /// # ABNF - /// - /// ```text - /// If-Unmodified-Since = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::Response; - /// use actix_http::http::header::IfUnmodifiedSince; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = Response::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfUnmodifiedSince(modified.into())); - /// ``` - (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate] - - test_if_unmodified_since { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } -} diff --git a/actix-http/src/header/common/last_modified.rs b/actix-http/src/header/common/last_modified.rs deleted file mode 100644 index cc888ccb0..000000000 --- a/actix-http/src/header/common/last_modified.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::header::{HttpDate, LAST_MODIFIED}; - -header! { - /// `Last-Modified` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) - /// - /// The `Last-Modified` header field in a response provides a timestamp - /// indicating the date and time at which the origin server believes the - /// selected representation was last modified, as determined at the - /// conclusion of handling the request. - /// - /// # ABNF - /// - /// ```text - /// Expires = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::Response; - /// use actix_http::http::header::LastModified; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = Response::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(LastModified(modified.into())); - /// ``` - (LastModified, LAST_MODIFIED) => [HttpDate] - - test_last_modified { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);} -} diff --git a/actix-http/src/header/common/mod.rs b/actix-http/src/header/common/mod.rs deleted file mode 100644 index 08950ea8b..000000000 --- a/actix-http/src/header/common/mod.rs +++ /dev/null @@ -1,352 +0,0 @@ -//! A Collection of Header implementations for common HTTP Headers. -//! -//! ## Mime -//! -//! Several header fields use MIME values for their contents. Keeping with the -//! strongly-typed theme, the [mime](https://docs.rs/mime) crate -//! is used, such as `ContentType(pub Mime)`. -#![cfg_attr(rustfmt, rustfmt_skip)] - -pub use self::accept_charset::AcceptCharset; -//pub use self::accept_encoding::AcceptEncoding; -pub use self::accept_language::AcceptLanguage; -pub use self::accept::Accept; -pub use self::allow::Allow; -pub use self::cache_control::{CacheControl, CacheDirective}; -pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam}; -pub use self::content_language::ContentLanguage; -pub use self::content_range::{ContentRange, ContentRangeSpec}; -pub use self::content_type::ContentType; -pub use self::date::Date; -pub use self::etag::ETag; -pub use self::expires::Expires; -pub use self::if_match::IfMatch; -pub use self::if_modified_since::IfModifiedSince; -pub use self::if_none_match::IfNoneMatch; -pub use self::if_range::IfRange; -pub use self::if_unmodified_since::IfUnmodifiedSince; -pub use self::last_modified::LastModified; -//pub use self::range::{Range, ByteRangeSpec}; - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__deref { - ($from:ty => $to:ty) => { - impl ::std::ops::Deref for $from { - type Target = $to; - - #[inline] - fn deref(&self) -> &$to { - &self.0 - } - } - - impl ::std::ops::DerefMut for $from { - #[inline] - fn deref_mut(&mut self) -> &mut $to { - &mut self.0 - } - } - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__tm { - ($id:ident, $tm:ident{$($tf:item)*}) => { - #[allow(unused_imports)] - #[cfg(test)] - mod $tm{ - use std::str; - use http::Method; - use mime::*; - use $crate::header::*; - use super::$id as HeaderField; - $($tf)* - } - - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! test_header { - ($id:ident, $raw:expr) => { - #[test] - fn $id() { - use $crate::test; - use super::*; - - let raw = $raw; - let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req = req.header(HeaderField::name(), item).take(); - } - let req = req.finish(); - let value = HeaderField::parse(&req); - let result = format!("{}", value.unwrap()); - let expected = String::from_utf8(raw[0].to_vec()).unwrap(); - let result_cmp: Vec = result - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - let expected_cmp: Vec = expected - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - assert_eq!(result_cmp.concat(), expected_cmp.concat()); - } - }; - ($id:ident, $raw:expr, $typed:expr) => { - #[test] - fn $id() { - use $crate::test; - - let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req.header(HeaderField::name(), item); - } - let req = req.finish(); - let val = HeaderField::parse(&req); - let typed: Option = $typed; - // Test parsing - assert_eq!(val.ok(), typed); - // Test formatting - if typed.is_some() { - let raw = &($raw)[..]; - let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap()); - let mut joined = String::new(); - joined.push_str(iter.next().unwrap()); - for s in iter { - joined.push_str(", "); - joined.push_str(s); - } - assert_eq!(format!("{}", typed.unwrap()), joined); - } - } - } -} - -#[macro_export] -macro_rules! header { - // $a:meta: Attributes associated with the header item (usually docs) - // $id:ident: Identifier of the header - // $n:expr: Lowercase name of the header - // $nn:expr: Nice name of the header - - // List header, zero or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)*) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::http::header::Header for $id { - #[inline] - fn name() -> $crate::http::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::http::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> ::std::fmt::Result { - $crate::http::header::fmt_comma_delimited(f, &self.0[..]) - } - } - impl $crate::http::header::IntoHeaderValue for $id { - type Error = $crate::http::header::InvalidHeaderValue; - - fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::http::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) - } - } - }; - // List header, one or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::http::header::Header for $id { - #[inline] - fn name() -> $crate::http::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::http::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - $crate::http::header::fmt_comma_delimited(f, &self.0[..]) - } - } - impl $crate::http::header::IntoHeaderValue for $id { - type Error = $crate::http::header::InvalidHeaderValue; - - fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::http::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) - } - } - }; - // Single value header - ($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub $value); - __hyper__deref!($id => $value); - impl $crate::http::header::Header for $id { - #[inline] - fn name() -> $crate::http::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::http::header::from_one_raw_str( - msg.headers().get(Self::name())).map($id) - } - } - impl std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self.0, f) - } - } - impl $crate::http::header::IntoHeaderValue for $id { - type Error = $crate::http::header::InvalidHeaderValue; - - fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - self.0.try_into() - } - } - }; - // List header, one or more items with "*" option - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub enum $id { - /// Any value is a match - Any, - /// Only the listed items are a match - Items(Vec<$item>), - } - impl $crate::http::header::Header for $id { - #[inline] - fn name() -> $crate::http::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - let any = msg.headers().get(Self::name()).and_then(|hdr| { - hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))}); - - if let Some(true) = any { - Ok($id::Any) - } else { - Ok($id::Items( - $crate::http::header::from_comma_delimited( - msg.headers().get_all(Self::name()))?)) - } - } - } - impl std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - $id::Any => f.write_str("*"), - $id::Items(ref fields) => $crate::http::header::fmt_comma_delimited( - f, &fields[..]) - } - } - } - impl $crate::http::header::IntoHeaderValue for $id { - type Error = $crate::http::header::InvalidHeaderValue; - - fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::http::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::http::header::HeaderValue::from_maybe_shared(writer.take()) - } - } - }; - - // optional test module - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => ($item)* - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => ($item)+ - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* ($id, $name) => [$item] - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => {Any / ($item)+} - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; -} - - -mod accept_charset; -//mod accept_encoding; -mod accept_language; -mod accept; -mod allow; -mod cache_control; -mod content_disposition; -mod content_language; -mod content_range; -mod content_type; -mod date; -mod etag; -mod expires; -mod if_match; -mod if_modified_since; -mod if_none_match; -mod if_range; -mod if_unmodified_since; -mod last_modified; diff --git a/actix-http/src/header/common/range.rs b/actix-http/src/header/common/range.rs deleted file mode 100644 index 71718fc7a..000000000 --- a/actix-http/src/header/common/range.rs +++ /dev/null @@ -1,434 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -use header::parsing::from_one_raw_str; -use header::{Header, Raw}; - -/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) -/// -/// The "Range" header field on a GET request modifies the method -/// semantics to request transfer of only one or more subranges of the -/// selected representation data, rather than the entire selected -/// representation data. -/// -/// # ABNF -/// -/// ```text -/// Range = byte-ranges-specifier / other-ranges-specifier -/// other-ranges-specifier = other-range-unit "=" other-range-set -/// other-range-set = 1*VCHAR -/// -/// bytes-unit = "bytes" -/// -/// byte-ranges-specifier = bytes-unit "=" byte-range-set -/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) -/// byte-range-spec = first-byte-pos "-" [last-byte-pos] -/// first-byte-pos = 1*DIGIT -/// last-byte-pos = 1*DIGIT -/// ``` -/// -/// # Example values -/// -/// * `bytes=1000-` -/// * `bytes=-2000` -/// * `bytes=0-1,30-40` -/// * `bytes=0-10,20-90,-100` -/// * `custom_unit=0-123` -/// * `custom_unit=xxx-yyy` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, Range, ByteRangeSpec}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::Bytes( -/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] -/// )); -/// -/// headers.clear(); -/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Range}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::bytes(1, 100)); -/// -/// headers.clear(); -/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)])); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub enum Range { - /// Byte range - Bytes(Vec), - /// Custom range, with unit not registered at IANA - /// (`other-range-unit`: String , `other-range-set`: String) - Unregistered(String, String), -} - -/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. -/// Each `ByteRangeSpec` defines a range of bytes to fetch -#[derive(PartialEq, Clone, Debug)] -pub enum ByteRangeSpec { - /// Get all bytes between x and y ("x-y") - FromTo(u64, u64), - /// Get all bytes starting from x ("x-") - AllFrom(u64), - /// Get last x bytes ("-x") - Last(u64), -} - -impl ByteRangeSpec { - /// Given the full length of the entity, attempt to normalize the byte range - /// into an satisfiable end-inclusive (from, to) range. - /// - /// The resulting range is guaranteed to be a satisfiable range within the - /// bounds of `0 <= from <= to < full_length`. - /// - /// If the byte range is deemed unsatisfiable, `None` is returned. - /// An unsatisfiable range is generally cause for a server to either reject - /// the client request with a `416 Range Not Satisfiable` status code, or to - /// simply ignore the range header and serve the full entity using a `200 - /// OK` status code. - /// - /// This function closely follows [RFC 7233][1] section 2.1. - /// As such, it considers ranges to be satisfiable if they meet the - /// following conditions: - /// - /// > If a valid byte-range-set includes at least one byte-range-spec with - /// a first-byte-pos that is less than the current length of the - /// representation, or at least one suffix-byte-range-spec with a - /// non-zero suffix-length, then the byte-range-set is satisfiable. - /// Otherwise, the byte-range-set is unsatisfiable. - /// - /// The function also computes remainder ranges based on the RFC: - /// - /// > If the last-byte-pos value is - /// absent, or if the value is greater than or equal to the current - /// length of the representation data, the byte range is interpreted as - /// the remainder of the representation (i.e., the server replaces the - /// value of last-byte-pos with a value that is one less than the current - /// length of the selected representation). - /// - /// [1]: https://tools.ietf.org/html/rfc7233 - pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { - // If the full length is zero, there is no satisfiable end-inclusive range. - if full_length == 0 { - return None; - } - match self { - &ByteRangeSpec::FromTo(from, to) => { - if from < full_length && from <= to { - Some((from, ::std::cmp::min(to, full_length - 1))) - } else { - None - } - } - &ByteRangeSpec::AllFrom(from) => { - if from < full_length { - Some((from, full_length - 1)) - } else { - None - } - } - &ByteRangeSpec::Last(last) => { - if last > 0 { - // From the RFC: If the selected representation is shorter - // than the specified suffix-length, - // the entire representation is used. - if last > full_length { - Some((0, full_length - 1)) - } else { - Some((full_length - last, full_length - 1)) - } - } else { - None - } - } - } - } -} - -impl Range { - /// Get the most common byte range header ("bytes=from-to") - pub fn bytes(from: u64, to: u64) -> Range { - Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) - } - - /// Get byte range header with multiple subranges - /// ("bytes=from1-to1,from2-to2,fromX-toX") - pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { - Range::Bytes( - ranges - .iter() - .map(|r| ByteRangeSpec::FromTo(r.0, r.1)) - .collect(), - ) - } -} - -impl fmt::Display for ByteRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), - ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), - ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), - } - } -} - -impl fmt::Display for Range { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Range::Bytes(ref ranges) => { - try!(write!(f, "bytes=")); - - for (i, range) in ranges.iter().enumerate() { - if i != 0 { - try!(f.write_str(",")); - } - try!(Display::fmt(range, f)); - } - Ok(()) - } - Range::Unregistered(ref unit, ref range_str) => { - write!(f, "{}={}", unit, range_str) - } - } - } -} - -impl FromStr for Range { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut iter = s.splitn(2, '='); - - match (iter.next(), iter.next()) { - (Some("bytes"), Some(ranges)) => { - let ranges = from_comma_delimited(ranges); - if ranges.is_empty() { - return Err(::Error::Header); - } - Ok(Range::Bytes(ranges)) - } - (Some(unit), Some(range_str)) if unit != "" && range_str != "" => Ok( - Range::Unregistered(unit.to_owned(), range_str.to_owned()), - ), - _ => Err(::Error::Header), - } - } -} - -impl FromStr for ByteRangeSpec { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut parts = s.splitn(2, '-'); - - match (parts.next(), parts.next()) { - (Some(""), Some(end)) => end.parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::Last), - (Some(start), Some("")) => start - .parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::AllFrom), - (Some(start), Some(end)) => match (start.parse(), end.parse()) { - (Ok(start), Ok(end)) if start <= end => { - Ok(ByteRangeSpec::FromTo(start, end)) - } - _ => Err(::Error::Header), - }, - _ => Err(::Error::Header), - } - } -} - -fn from_comma_delimited(s: &str) -> Vec { - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }) - .filter_map(|x| x.parse().ok()) - .collect() -} - -impl Header for Range { - fn header_name() -> &'static str { - static NAME: &'static str = "Range"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - from_one_raw_str(raw) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -#[test] -fn test_parse_bytes_range_valid() { - let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); - let r3 = Range::bytes(1, 100); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); - let r2: Range = - Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); - let r3 = Range::Bytes(vec![ - ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::AllFrom(200), - ]); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); - let r3 = Range::Bytes(vec![ - ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::Last(100), - ]); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); - assert_eq!(r, r2); -} - -#[test] -fn test_parse_unregistered_range_valid() { - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); - assert_eq!(r, r2); - - let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); - assert_eq!(r, r2); - - let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); - assert_eq!(r, r2); -} - -#[test] -fn test_parse_invalid() { - let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"abc".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"custom=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"=1-100".into()); - assert_eq!(r.ok(), None); -} - -#[test] -fn test_fmt() { - use header::Headers; - - let mut headers = Headers::new(); - - headers.set(Range::Bytes(vec![ - ByteRangeSpec::FromTo(0, 1000), - ByteRangeSpec::AllFrom(2000), - ])); - assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); - - headers.clear(); - headers.set(Range::Bytes(vec![])); - - assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); - - headers.clear(); - headers.set(Range::Unregistered( - "custom".to_owned(), - "1-xxx".to_owned(), - )); - - assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); -} - -#[test] -fn test_byte_range_spec_to_satisfiable_range() { - assert_eq!( - Some((0, 0)), - ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3) - ); - assert_eq!( - Some((1, 2)), - ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3) - ); - assert_eq!( - Some((1, 2)), - ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0) - ); - - assert_eq!( - Some((0, 2)), - ByteRangeSpec::AllFrom(0).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::AllFrom(2).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(3).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(5).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(0).to_satisfiable_range(0) - ); - - assert_eq!( - Some((1, 2)), - ByteRangeSpec::Last(2).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::Last(1).to_satisfiable_range(3) - ); - assert_eq!( - Some((0, 2)), - ByteRangeSpec::Last(5).to_satisfiable_range(3) - ); - assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0)); -} diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs deleted file mode 100644 index 132087b9e..000000000 --- a/actix-http/src/header/map.rs +++ /dev/null @@ -1,385 +0,0 @@ -use std::collections::hash_map::{self, Entry}; -use std::convert::TryFrom; - -use either::Either; -use fxhash::FxHashMap; -use http::header::{HeaderName, HeaderValue}; - -/// A set of HTTP headers -/// -/// `HeaderMap` is an multimap of [`HeaderName`] to values. -/// -/// [`HeaderName`]: struct.HeaderName.html -#[derive(Debug, Clone)] -pub struct HeaderMap { - pub(crate) inner: FxHashMap, -} - -#[derive(Debug, Clone)] -pub(crate) enum Value { - One(HeaderValue), - Multi(Vec), -} - -impl Value { - fn get(&self) -> &HeaderValue { - match self { - Value::One(ref val) => val, - Value::Multi(ref val) => &val[0], - } - } - - fn get_mut(&mut self) -> &mut HeaderValue { - match self { - Value::One(ref mut val) => val, - Value::Multi(ref mut val) => &mut val[0], - } - } - - fn append(&mut self, val: HeaderValue) { - match self { - Value::One(_) => { - let data = std::mem::replace(self, Value::Multi(vec![val])); - match data { - Value::One(val) => self.append(val), - Value::Multi(_) => unreachable!(), - } - } - Value::Multi(ref mut vec) => vec.push(val), - } - } -} - -impl HeaderMap { - /// Create an empty `HeaderMap`. - /// - /// The map will be created without any capacity. This function will not - /// allocate. - pub fn new() -> Self { - HeaderMap { - inner: FxHashMap::default(), - } - } - - /// Create an empty `HeaderMap` with the specified capacity. - /// - /// The returned map will allocate internal storage in order to hold about - /// `capacity` elements without reallocating. However, this is a "best - /// effort" as there are usage patterns that could cause additional - /// allocations before `capacity` headers are stored in the map. - /// - /// More capacity than requested may be allocated. - pub fn with_capacity(capacity: usize) -> HeaderMap { - HeaderMap { - inner: FxHashMap::with_capacity_and_hasher(capacity, Default::default()), - } - } - - /// Returns the number of keys stored in the map. - /// - /// This number could be be less than or equal to actual headers stored in - /// the map. - pub fn len(&self) -> usize { - self.inner.len() - } - - /// Returns true if the map contains no elements. - pub fn is_empty(&self) -> bool { - self.inner.len() == 0 - } - - /// Clears the map, removing all key-value pairs. Keeps the allocated memory - /// for reuse. - pub fn clear(&mut self) { - self.inner.clear(); - } - - /// Returns the number of headers the map can hold without reallocating. - /// - /// This number is an approximation as certain usage patterns could cause - /// additional allocations before the returned capacity is filled. - pub fn capacity(&self) -> usize { - self.inner.capacity() - } - - /// Reserves capacity for at least `additional` more headers to be inserted - /// into the `HeaderMap`. - /// - /// The header map may reserve more space to avoid frequent reallocations. - /// Like with `with_capacity`, this will be a "best effort" to avoid - /// allocations until `additional` more headers are inserted. Certain usage - /// patterns could cause additional allocations before the number is - /// reached. - pub fn reserve(&mut self, additional: usize) { - self.inner.reserve(additional) - } - - /// Returns a reference to the value associated with the key. - /// - /// If there are multiple values associated with the key, then the first one - /// is returned. Use `get_all` to get all values associated with a given - /// key. Returns `None` if there are no values associated with the key. - pub fn get(&self, name: N) -> Option<&HeaderValue> { - self.get2(name).map(|v| v.get()) - } - - fn get2(&self, name: N) -> Option<&Value> { - match name.as_name() { - Either::Left(name) => self.inner.get(name), - Either::Right(s) => { - if let Ok(name) = HeaderName::try_from(s) { - self.inner.get(&name) - } else { - None - } - } - } - } - - /// Returns a view of all values associated with a key. - /// - /// The returned view does not incur any allocations and allows iterating - /// the values associated with the key. See [`GetAll`] for more details. - /// Returns `None` if there are no values associated with the key. - /// - /// [`GetAll`]: struct.GetAll.html - pub fn get_all(&self, name: N) -> GetAll<'_> { - GetAll { - idx: 0, - item: self.get2(name), - } - } - - /// Returns a mutable reference to the value associated with the key. - /// - /// If there are multiple values associated with the key, then the first one - /// is returned. Use `entry` to get all values associated with a given - /// key. Returns `None` if there are no values associated with the key. - pub fn get_mut(&mut self, name: N) -> Option<&mut HeaderValue> { - match name.as_name() { - Either::Left(name) => self.inner.get_mut(name).map(|v| v.get_mut()), - Either::Right(s) => { - if let Ok(name) = HeaderName::try_from(s) { - self.inner.get_mut(&name).map(|v| v.get_mut()) - } else { - None - } - } - } - } - - /// Returns true if the map contains a value for the specified key. - pub fn contains_key(&self, key: N) -> bool { - match key.as_name() { - Either::Left(name) => self.inner.contains_key(name), - Either::Right(s) => { - if let Ok(name) = HeaderName::try_from(s) { - self.inner.contains_key(&name) - } else { - false - } - } - } - } - - /// An iterator visiting all key-value pairs. - /// - /// The iteration order is arbitrary, but consistent across platforms for - /// the same crate version. Each key will be yielded once per associated - /// value. So, if a key has 3 associated values, it will be yielded 3 times. - pub fn iter(&self) -> Iter<'_> { - Iter::new(self.inner.iter()) - } - - /// An iterator visiting all keys. - /// - /// The iteration order is arbitrary, but consistent across platforms for - /// the same crate version. Each key will be yielded only once even if it - /// has multiple associated values. - pub fn keys(&self) -> Keys<'_> { - Keys(self.inner.keys()) - } - - /// Inserts a key-value pair into the map. - /// - /// If the map did not previously have this key present, then `None` is - /// returned. - /// - /// If the map did have this key present, the new value is associated with - /// the key and all previous values are removed. **Note** that only a single - /// one of the previous values is returned. If there are multiple values - /// that have been previously associated with the key, then the first one is - /// returned. See `insert_mult` on `OccupiedEntry` for an API that returns - /// all values. - /// - /// The key is not updated, though; this matters for types that can be `==` - /// without being identical. - pub fn insert(&mut self, key: HeaderName, val: HeaderValue) { - let _ = self.inner.insert(key, Value::One(val)); - } - - /// Inserts a key-value pair into the map. - /// - /// If the map did not previously have this key present, then `false` is - /// returned. - /// - /// If the map did have this key present, the new value is pushed to the end - /// of the list of values currently associated with the key. The key is not - /// updated, though; this matters for types that can be `==` without being - /// identical. - pub fn append(&mut self, key: HeaderName, value: HeaderValue) { - match self.inner.entry(key) { - Entry::Occupied(mut entry) => entry.get_mut().append(value), - Entry::Vacant(entry) => { - entry.insert(Value::One(value)); - } - } - } - - /// Removes all headers for a particular header name from the map. - pub fn remove(&mut self, key: N) { - match key.as_name() { - Either::Left(name) => { - let _ = self.inner.remove(name); - } - Either::Right(s) => { - if let Ok(name) = HeaderName::try_from(s) { - let _ = self.inner.remove(&name); - } - } - } - } -} - -#[doc(hidden)] -pub trait AsName { - fn as_name(&self) -> Either<&HeaderName, &str>; -} - -impl AsName for HeaderName { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Left(self) - } -} - -impl<'a> AsName for &'a HeaderName { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Left(self) - } -} - -impl<'a> AsName for &'a str { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Right(self) - } -} - -impl AsName for String { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Right(self.as_str()) - } -} - -impl<'a> AsName for &'a String { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Right(self.as_str()) - } -} - -pub struct GetAll<'a> { - idx: usize, - item: Option<&'a Value>, -} - -impl<'a> Iterator for GetAll<'a> { - type Item = &'a HeaderValue; - - #[inline] - fn next(&mut self) -> Option<&'a HeaderValue> { - if let Some(ref val) = self.item { - match val { - Value::One(ref val) => { - self.item.take(); - Some(val) - } - Value::Multi(ref vec) => { - if self.idx < vec.len() { - let item = Some(&vec[self.idx]); - self.idx += 1; - item - } else { - self.item.take(); - None - } - } - } - } else { - None - } - } -} - -pub struct Keys<'a>(hash_map::Keys<'a, HeaderName, Value>); - -impl<'a> Iterator for Keys<'a> { - type Item = &'a HeaderName; - - #[inline] - fn next(&mut self) -> Option<&'a HeaderName> { - self.0.next() - } -} - -impl<'a> IntoIterator for &'a HeaderMap { - type Item = (&'a HeaderName, &'a HeaderValue); - type IntoIter = Iter<'a>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -pub struct Iter<'a> { - idx: usize, - current: Option<(&'a HeaderName, &'a Vec)>, - iter: hash_map::Iter<'a, HeaderName, Value>, -} - -impl<'a> Iter<'a> { - fn new(iter: hash_map::Iter<'a, HeaderName, Value>) -> Self { - Self { - iter, - idx: 0, - current: None, - } - } -} - -impl<'a> Iterator for Iter<'a> { - type Item = (&'a HeaderName, &'a HeaderValue); - - #[inline] - fn next(&mut self) -> Option<(&'a HeaderName, &'a HeaderValue)> { - if let Some(ref mut item) = self.current { - if self.idx < item.1.len() { - let item = (item.0, &item.1[self.idx]); - self.idx += 1; - return Some(item); - } else { - self.idx = 0; - self.current.take(); - } - } - if let Some(item) = self.iter.next() { - match item.1 { - Value::One(ref value) => Some((item.0, value)), - Value::Multi(ref vec) => { - self.current = Some((item.0, vec)); - self.next() - } - } - } else { - None - } - } -} diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs deleted file mode 100644 index 0db26ceb0..000000000 --- a/actix-http/src/header/mod.rs +++ /dev/null @@ -1,505 +0,0 @@ -//! Various http headers -// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header) - -use std::convert::TryFrom; -use std::{fmt, str::FromStr}; - -use bytes::{Bytes, BytesMut}; -use http::Error as HttpError; -use mime::Mime; -use percent_encoding::{AsciiSet, CONTROLS}; - -pub use http::header::*; - -use crate::error::ParseError; -use crate::httpmessage::HttpMessage; - -mod common; -pub(crate) mod map; -mod shared; -pub use self::common::*; -#[doc(hidden)] -pub use self::shared::*; - -#[doc(hidden)] -pub use self::map::GetAll; -pub use self::map::HeaderMap; - -/// A trait for any object that will represent a header field and value. -pub trait Header -where - Self: IntoHeaderValue, -{ - /// Returns the name of the header field - fn name() -> HeaderName; - - /// Parse a header - fn parse(msg: &T) -> Result; -} - -/// A trait for any object that can be Converted to a `HeaderValue` -pub trait IntoHeaderValue: Sized { - /// The type returned in the event of a conversion error. - type Error: Into; - - /// Try to convert value to a Header value. - fn try_into(self) -> Result; -} - -impl IntoHeaderValue for HeaderValue { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - Ok(self) - } -} - -impl<'a> IntoHeaderValue for &'a str { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - self.parse() - } -} - -impl<'a> IntoHeaderValue for &'a [u8] { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_bytes(self) - } -} - -impl IntoHeaderValue for Bytes { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_maybe_shared(self) - } -} - -impl IntoHeaderValue for Vec { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::try_from(self) - } -} - -impl IntoHeaderValue for String { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::try_from(self) - } -} - -impl IntoHeaderValue for usize { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - let s = format!("{}", self); - HeaderValue::try_from(s) - } -} - -impl IntoHeaderValue for u64 { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - let s = format!("{}", self); - HeaderValue::try_from(s) - } -} - -impl IntoHeaderValue for Mime { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::try_from(format!("{}", self)) - } -} - -/// Represents supported types of content encodings -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ContentEncoding { - /// Automatically select encoding based on encoding negotiation - Auto, - /// A format using the Brotli algorithm - Br, - /// A format using the zlib structure with deflate algorithm - Deflate, - /// Gzip algorithm - Gzip, - /// Indicates the identity function (i.e. no compression, nor modification) - Identity, -} - -impl ContentEncoding { - #[inline] - /// Is the content compressed? - pub fn is_compression(self) -> bool { - match self { - ContentEncoding::Identity | ContentEncoding::Auto => false, - _ => true, - } - } - - #[inline] - /// Convert content encoding to string - pub fn as_str(self) -> &'static str { - match self { - ContentEncoding::Br => "br", - ContentEncoding::Gzip => "gzip", - ContentEncoding::Deflate => "deflate", - ContentEncoding::Identity | ContentEncoding::Auto => "identity", - } - } - - #[inline] - /// default quality value - pub fn quality(self) -> f64 { - match self { - ContentEncoding::Br => 1.1, - ContentEncoding::Gzip => 1.0, - ContentEncoding::Deflate => 0.9, - ContentEncoding::Identity | ContentEncoding::Auto => 0.1, - } - } -} - -impl<'a> From<&'a str> for ContentEncoding { - fn from(s: &'a str) -> ContentEncoding { - let s = s.trim(); - - if s.eq_ignore_ascii_case("br") { - ContentEncoding::Br - } else if s.eq_ignore_ascii_case("gzip") { - ContentEncoding::Gzip - } else if s.eq_ignore_ascii_case("deflate") { - ContentEncoding::Deflate - } else { - ContentEncoding::Identity - } - } -} - -#[doc(hidden)] -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::new(), - } - } - fn take(&mut self) -> Bytes { - self.buf.split().freeze() - } -} - -impl fmt::Write for Writer { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - self.buf.extend_from_slice(s.as_bytes()); - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { - fmt::write(self, args) - } -} - -#[inline] -#[doc(hidden)] -/// Reads a comma-delimited raw header into a Vec. -pub fn from_comma_delimited<'a, I: Iterator + 'a, T: FromStr>( - all: I, -) -> Result, ParseError> { - let mut result = Vec::new(); - for h in all { - let s = h.to_str().map_err(|_| ParseError::Header)?; - result.extend( - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }) - .filter_map(|x| x.trim().parse().ok()), - ) - } - Ok(result) -} - -#[inline] -#[doc(hidden)] -/// Reads a single string when parsing a header. -pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { - if let Some(line) = val { - let line = line.to_str().map_err(|_| ParseError::Header)?; - if !line.is_empty() { - return T::from_str(line).or(Err(ParseError::Header)); - } - } - Err(ParseError::Header) -} - -#[inline] -#[doc(hidden)] -/// Format an array into a comma-delimited string. -pub fn fmt_comma_delimited(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result -where - T: fmt::Display, -{ - let mut iter = parts.iter(); - if let Some(part) = iter.next() { - fmt::Display::fmt(part, f)?; - } - for part in iter { - f.write_str(", ")?; - fmt::Display::fmt(part, f)?; - } - Ok(()) -} - -// From hyper v0.11.27 src/header/parsing.rs - -/// The value part of an extended parameter consisting of three parts: -/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`), -/// and a character sequence representing the actual value (`value`), separated by single quote -/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -#[derive(Clone, Debug, PartialEq)] -pub struct ExtendedValue { - /// The character set that is used to encode the `value` to a string. - pub charset: Charset, - /// The human language details of the `value`, if available. - pub language_tag: Option, - /// The parameter value, as expressed in octets. - pub value: Vec, -} - -/// Parses extended header parameter values (`ext-value`), as defined in -/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -/// -/// Extended values are denoted by parameter names that end with `*`. -/// -/// ## ABNF -/// -/// ```text -/// ext-value = charset "'" [ language ] "'" value-chars -/// ; like RFC 2231's -/// ; (see [RFC2231], Section 7) -/// -/// charset = "UTF-8" / "ISO-8859-1" / mime-charset -/// -/// mime-charset = 1*mime-charsetc -/// mime-charsetc = ALPHA / DIGIT -/// / "!" / "#" / "$" / "%" / "&" -/// / "+" / "-" / "^" / "_" / "`" -/// / "{" / "}" / "~" -/// ; as in Section 2.3 of [RFC2978] -/// ; except that the single quote is not included -/// ; SHOULD be registered in the IANA charset registry -/// -/// language = -/// -/// value-chars = *( pct-encoded / attr-char ) -/// -/// pct-encoded = "%" HEXDIG HEXDIG -/// ; see [RFC3986], Section 2.1 -/// -/// attr-char = ALPHA / DIGIT -/// / "!" / "#" / "$" / "&" / "+" / "-" / "." -/// / "^" / "_" / "`" / "|" / "~" -/// ; token except ( "*" / "'" / "%" ) -/// ``` -pub fn parse_extended_value( - val: &str, -) -> Result { - // Break into three pieces separated by the single-quote character - let mut parts = val.splitn(3, '\''); - - // Interpret the first piece as a Charset - let charset: Charset = match parts.next() { - None => return Err(crate::error::ParseError::Header), - Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?, - }; - - // Interpret the second piece as a language tag - let language_tag: Option = match parts.next() { - None => return Err(crate::error::ParseError::Header), - Some("") => None, - Some(s) => match s.parse() { - Ok(lt) => Some(lt), - Err(_) => return Err(crate::error::ParseError::Header), - }, - }; - - // Interpret the third piece as a sequence of value characters - let value: Vec = match parts.next() { - None => return Err(crate::error::ParseError::Header), - Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), - }; - - Ok(ExtendedValue { - value, - charset, - language_tag, - }) -} - -impl fmt::Display for ExtendedValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let encoded_value = - percent_encoding::percent_encode(&self.value[..], HTTP_VALUE); - if let Some(ref lang) = self.language_tag { - write!(f, "{}'{}'{}", self.charset, lang, encoded_value) - } else { - write!(f, "{}''{}", self.charset, encoded_value) - } - } -} - -/// Percent encode a sequence of bytes with a character set defined in -/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] -/// -/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 -pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { - let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); - fmt::Display::fmt(&encoded, f) -} - -/// Convert http::HeaderMap to a HeaderMap -impl From for HeaderMap { - fn from(map: http::HeaderMap) -> HeaderMap { - let mut new_map = HeaderMap::with_capacity(map.capacity()); - for (h, v) in map.iter() { - new_map.append(h.clone(), v.clone()); - } - new_map - } -} - -// This encode set is used for HTTP header values and is defined at -// https://tools.ietf.org/html/rfc5987#section-3.2 -pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS - .add(b' ') - .add(b'"') - .add(b'%') - .add(b'\'') - .add(b'(') - .add(b')') - .add(b'*') - .add(b',') - .add(b'/') - .add(b':') - .add(b';') - .add(b'<') - .add(b'-') - .add(b'>') - .add(b'?') - .add(b'[') - .add(b'\\') - .add(b']') - .add(b'{') - .add(b'}'); - -#[cfg(test)] -mod tests { - use super::shared::Charset; - use super::{parse_extended_value, ExtendedValue}; - use language_tags::LanguageTag; - - #[test] - fn test_parse_extended_value_with_encoding_and_language_tag() { - let expected_language_tag = "en".parse::().unwrap(); - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode character U+00A3 (POUND SIGN) - let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Iso_8859_1, extended_value.charset); - assert!(extended_value.language_tag.is_some()); - assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); - assert_eq!( - vec![163, b' ', b'r', b'a', b't', b'e', b's'], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_with_encoding() { - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) - // and U+20AC (EURO SIGN) - let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); - assert!(extended_value.language_tag.is_none()); - assert_eq!( - vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_missing_language_tag_and_encoding() { - // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 - let result = parse_extended_value("foo%20bar.html"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted() { - let result = parse_extended_value("UTF-8'missing third part"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted_blank() { - let result = parse_extended_value("blank second part'"); - assert!(result.is_err()); - } - - #[test] - fn test_fmt_extended_value_with_encoding_and_language_tag() { - let extended_value = ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: Some("en".parse().expect("Could not parse language tag")), - value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], - }; - assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); - } - - #[test] - fn test_fmt_extended_value_with_encoding() { - let extended_value = ExtendedValue { - charset: Charset::Ext("UTF-8".to_string()), - language_tag: None, - value: vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - }; - assert_eq!( - "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", - format!("{}", extended_value) - ); - } -} diff --git a/actix-http/src/header/shared/charset.rs b/actix-http/src/header/shared/charset.rs deleted file mode 100644 index 6ddfa03ea..000000000 --- a/actix-http/src/header/shared/charset.rs +++ /dev/null @@ -1,153 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -use self::Charset::*; - -/// A Mime charset. -/// -/// The string representation is normalized to upper case. -/// -/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. -/// -/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml -#[derive(Clone, Debug, PartialEq)] -#[allow(non_camel_case_types)] -pub enum Charset { - /// US ASCII - Us_Ascii, - /// ISO-8859-1 - Iso_8859_1, - /// ISO-8859-2 - Iso_8859_2, - /// ISO-8859-3 - Iso_8859_3, - /// ISO-8859-4 - Iso_8859_4, - /// ISO-8859-5 - Iso_8859_5, - /// ISO-8859-6 - Iso_8859_6, - /// ISO-8859-7 - Iso_8859_7, - /// ISO-8859-8 - Iso_8859_8, - /// ISO-8859-9 - Iso_8859_9, - /// ISO-8859-10 - Iso_8859_10, - /// Shift_JIS - Shift_Jis, - /// EUC-JP - Euc_Jp, - /// ISO-2022-KR - Iso_2022_Kr, - /// EUC-KR - Euc_Kr, - /// ISO-2022-JP - Iso_2022_Jp, - /// ISO-2022-JP-2 - Iso_2022_Jp_2, - /// ISO-8859-6-E - Iso_8859_6_E, - /// ISO-8859-6-I - Iso_8859_6_I, - /// ISO-8859-8-E - Iso_8859_8_E, - /// ISO-8859-8-I - Iso_8859_8_I, - /// GB2312 - Gb2312, - /// Big5 - Big5, - /// KOI8-R - Koi8_R, - /// An arbitrary charset specified as a string - Ext(String), -} - -impl Charset { - fn label(&self) -> &str { - match *self { - Us_Ascii => "US-ASCII", - Iso_8859_1 => "ISO-8859-1", - Iso_8859_2 => "ISO-8859-2", - Iso_8859_3 => "ISO-8859-3", - Iso_8859_4 => "ISO-8859-4", - Iso_8859_5 => "ISO-8859-5", - Iso_8859_6 => "ISO-8859-6", - Iso_8859_7 => "ISO-8859-7", - Iso_8859_8 => "ISO-8859-8", - Iso_8859_9 => "ISO-8859-9", - Iso_8859_10 => "ISO-8859-10", - Shift_Jis => "Shift-JIS", - Euc_Jp => "EUC-JP", - Iso_2022_Kr => "ISO-2022-KR", - Euc_Kr => "EUC-KR", - Iso_2022_Jp => "ISO-2022-JP", - Iso_2022_Jp_2 => "ISO-2022-JP-2", - Iso_8859_6_E => "ISO-8859-6-E", - Iso_8859_6_I => "ISO-8859-6-I", - Iso_8859_8_E => "ISO-8859-8-E", - Iso_8859_8_I => "ISO-8859-8-I", - Gb2312 => "GB2312", - Big5 => "big5", - Koi8_R => "KOI8-R", - Ext(ref s) => s, - } - } -} - -impl Display for Charset { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.label()) - } -} - -impl FromStr for Charset { - type Err = crate::Error; - - fn from_str(s: &str) -> crate::Result { - Ok(match s.to_ascii_uppercase().as_ref() { - "US-ASCII" => Us_Ascii, - "ISO-8859-1" => Iso_8859_1, - "ISO-8859-2" => Iso_8859_2, - "ISO-8859-3" => Iso_8859_3, - "ISO-8859-4" => Iso_8859_4, - "ISO-8859-5" => Iso_8859_5, - "ISO-8859-6" => Iso_8859_6, - "ISO-8859-7" => Iso_8859_7, - "ISO-8859-8" => Iso_8859_8, - "ISO-8859-9" => Iso_8859_9, - "ISO-8859-10" => Iso_8859_10, - "SHIFT-JIS" => Shift_Jis, - "EUC-JP" => Euc_Jp, - "ISO-2022-KR" => Iso_2022_Kr, - "EUC-KR" => Euc_Kr, - "ISO-2022-JP" => Iso_2022_Jp, - "ISO-2022-JP-2" => Iso_2022_Jp_2, - "ISO-8859-6-E" => Iso_8859_6_E, - "ISO-8859-6-I" => Iso_8859_6_I, - "ISO-8859-8-E" => Iso_8859_8_E, - "ISO-8859-8-I" => Iso_8859_8_I, - "GB2312" => Gb2312, - "big5" => Big5, - "KOI8-R" => Koi8_R, - s => Ext(s.to_owned()), - }) - } -} - -#[test] -fn test_parse() { - assert_eq!(Us_Ascii, "us-ascii".parse().unwrap()); - assert_eq!(Us_Ascii, "US-Ascii".parse().unwrap()); - assert_eq!(Us_Ascii, "US-ASCII".parse().unwrap()); - assert_eq!(Shift_Jis, "Shift-JIS".parse().unwrap()); - assert_eq!(Ext("ABCD".to_owned()), "abcd".parse().unwrap()); -} - -#[test] -fn test_display() { - assert_eq!("US-ASCII", format!("{}", Us_Ascii)); - assert_eq!("ABCD", format!("{}", Ext("ABCD".to_owned()))); -} diff --git a/actix-http/src/header/shared/encoding.rs b/actix-http/src/header/shared/encoding.rs deleted file mode 100644 index aa49dea45..000000000 --- a/actix-http/src/header/shared/encoding.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::{fmt, str}; - -pub use self::Encoding::{ - Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, -}; - -/// A value to represent an encoding used in `Transfer-Encoding` -/// or `Accept-Encoding` header. -#[derive(Clone, PartialEq, Debug)] -pub enum Encoding { - /// The `chunked` encoding. - Chunked, - /// The `br` encoding. - Brotli, - /// The `gzip` encoding. - Gzip, - /// The `deflate` encoding. - Deflate, - /// The `compress` encoding. - Compress, - /// The `identity` encoding. - Identity, - /// The `trailers` encoding. - Trailers, - /// Some other encoding that is less common, can be any String. - EncodingExt(String), -} - -impl fmt::Display for Encoding { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Chunked => "chunked", - Brotli => "br", - Gzip => "gzip", - Deflate => "deflate", - Compress => "compress", - Identity => "identity", - Trailers => "trailers", - EncodingExt(ref s) => s.as_ref(), - }) - } -} - -impl str::FromStr for Encoding { - type Err = crate::error::ParseError; - fn from_str(s: &str) -> Result { - match s { - "chunked" => Ok(Chunked), - "br" => Ok(Brotli), - "deflate" => Ok(Deflate), - "gzip" => Ok(Gzip), - "compress" => Ok(Compress), - "identity" => Ok(Identity), - "trailers" => Ok(Trailers), - _ => Ok(EncodingExt(s.to_owned())), - } - } -} diff --git a/actix-http/src/header/shared/entity.rs b/actix-http/src/header/shared/entity.rs deleted file mode 100644 index 3525a19c6..000000000 --- a/actix-http/src/header/shared/entity.rs +++ /dev/null @@ -1,265 +0,0 @@ -use std::fmt::{self, Display, Write}; -use std::str::FromStr; - -use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; - -/// check that each char in the slice is either: -/// 1. `%x21`, or -/// 2. in the range `%x23` to `%x7E`, or -/// 3. above `%x80` -fn check_slice_validity(slice: &str) -> bool { - slice - .bytes() - .all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) -} - -/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) -/// -/// An entity tag consists of a string enclosed by two literal double quotes. -/// Preceding the first double quote is an optional weakness indicator, -/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and -/// `W/"xyzzy"`. -/// -/// # ABNF -/// -/// ```text -/// entity-tag = [ weak ] opaque-tag -/// weak = %x57.2F ; "W/", case-sensitive -/// opaque-tag = DQUOTE *etagc DQUOTE -/// etagc = %x21 / %x23-7E / obs-text -/// ; VCHAR except double quotes, plus obs-text -/// ``` -/// -/// # Comparison -/// To check if two entity tags are equivalent in an application always use the -/// `strong_eq` or `weak_eq` methods based on the context of the Tag. Only use -/// `==` to check if two tags are identical. -/// -/// The example below shows the results for a set of entity-tag pairs and -/// both the weak and strong comparison function results: -/// -/// | `ETag 1`| `ETag 2`| Strong Comparison | Weak Comparison | -/// |---------|---------|-------------------|-----------------| -/// | `W/"1"` | `W/"1"` | no match | match | -/// | `W/"1"` | `W/"2"` | no match | no match | -/// | `W/"1"` | `"1"` | no match | match | -/// | `"1"` | `"1"` | match | match | -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct EntityTag { - /// Weakness indicator for the tag - pub weak: bool, - /// The opaque string in between the DQUOTEs - tag: String, -} - -impl EntityTag { - /// Constructs a new EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn new(weak: bool, tag: String) -> EntityTag { - assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - EntityTag { weak, tag } - } - - /// Constructs a new weak EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn weak(tag: String) -> EntityTag { - EntityTag::new(true, tag) - } - - /// Constructs a new strong EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn strong(tag: String) -> EntityTag { - EntityTag::new(false, tag) - } - - /// Get the tag. - pub fn tag(&self) -> &str { - self.tag.as_ref() - } - - /// Set the tag. - /// # Panics - /// If the tag contains invalid characters. - pub fn set_tag(&mut self, tag: String) { - assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - self.tag = tag - } - - /// For strong comparison two entity-tags are equivalent if both are not - /// weak and their opaque-tags match character-by-character. - pub fn strong_eq(&self, other: &EntityTag) -> bool { - !self.weak && !other.weak && self.tag == other.tag - } - - /// For weak comparison two entity-tags are equivalent if their - /// opaque-tags match character-by-character, regardless of either or - /// both being tagged as "weak". - pub fn weak_eq(&self, other: &EntityTag) -> bool { - self.tag == other.tag - } - - /// The inverse of `EntityTag.strong_eq()`. - pub fn strong_ne(&self, other: &EntityTag) -> bool { - !self.strong_eq(other) - } - - /// The inverse of `EntityTag.weak_eq()`. - pub fn weak_ne(&self, other: &EntityTag) -> bool { - !self.weak_eq(other) - } -} - -impl Display for EntityTag { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.weak { - write!(f, "W/\"{}\"", self.tag) - } else { - write!(f, "\"{}\"", self.tag) - } - } -} - -impl FromStr for EntityTag { - type Err = crate::error::ParseError; - - fn from_str(s: &str) -> Result { - let length: usize = s.len(); - let slice = &s[..]; - // Early exits if it doesn't terminate in a DQUOTE. - if !slice.ends_with('"') || slice.len() < 2 { - return Err(crate::error::ParseError::Header); - } - // The etag is weak if its first char is not a DQUOTE. - if slice.len() >= 2 - && slice.starts_with('"') - && check_slice_validity(&slice[1..length - 1]) - { - // No need to check if the last char is a DQUOTE, - // we already did that above. - return Ok(EntityTag { - weak: false, - tag: slice[1..length - 1].to_owned(), - }); - } else if slice.len() >= 4 - && slice.starts_with("W/\"") - && check_slice_validity(&slice[3..length - 1]) - { - return Ok(EntityTag { - weak: true, - tag: slice[3..length - 1].to_owned(), - }); - } - Err(crate::error::ParseError::Header) - } -} - -impl IntoHeaderValue for EntityTag { - type Error = InvalidHeaderValue; - - fn try_into(self) -> Result { - let mut wrt = Writer::new(); - write!(wrt, "{}", self).unwrap(); - HeaderValue::from_maybe_shared(wrt.take()) - } -} - -#[cfg(test)] -mod tests { - use super::EntityTag; - - #[test] - fn test_etag_parse_success() { - // Expected success - assert_eq!( - "\"foobar\"".parse::().unwrap(), - EntityTag::strong("foobar".to_owned()) - ); - assert_eq!( - "\"\"".parse::().unwrap(), - EntityTag::strong("".to_owned()) - ); - assert_eq!( - "W/\"weaktag\"".parse::().unwrap(), - EntityTag::weak("weaktag".to_owned()) - ); - assert_eq!( - "W/\"\x65\x62\"".parse::().unwrap(), - EntityTag::weak("\x65\x62".to_owned()) - ); - assert_eq!( - "W/\"\"".parse::().unwrap(), - EntityTag::weak("".to_owned()) - ); - } - - #[test] - fn test_etag_parse_failures() { - // Expected failures - assert!("no-dquotes".parse::().is_err()); - assert!("w/\"the-first-w-is-case-sensitive\"" - .parse::() - .is_err()); - assert!("".parse::().is_err()); - assert!("\"unmatched-dquotes1".parse::().is_err()); - assert!("unmatched-dquotes2\"".parse::().is_err()); - assert!("matched-\"dquotes\"".parse::().is_err()); - } - - #[test] - fn test_etag_fmt() { - assert_eq!( - format!("{}", EntityTag::strong("foobar".to_owned())), - "\"foobar\"" - ); - assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); - assert_eq!( - format!("{}", EntityTag::weak("weak-etag".to_owned())), - "W/\"weak-etag\"" - ); - assert_eq!( - format!("{}", EntityTag::weak("\u{0065}".to_owned())), - "W/\"\x65\"" - ); - assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); - } - - #[test] - fn test_cmp() { - // | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | - // |---------|---------|-------------------|-----------------| - // | `W/"1"` | `W/"1"` | no match | match | - // | `W/"1"` | `W/"2"` | no match | no match | - // | `W/"1"` | `"1"` | no match | match | - // | `"1"` | `"1"` | match | match | - let mut etag1 = EntityTag::weak("1".to_owned()); - let mut etag2 = EntityTag::weak("1".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::weak("2".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(!etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(etag1.weak_ne(&etag2)); - - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - - etag1 = EntityTag::strong("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); - assert!(etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(!etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - } -} diff --git a/actix-http/src/header/shared/httpdate.rs b/actix-http/src/header/shared/httpdate.rs deleted file mode 100644 index 1b52f0de4..000000000 --- a/actix-http/src/header/shared/httpdate.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::fmt::{self, Display}; -use std::io::Write; -use std::str::FromStr; -use std::time::{SystemTime, UNIX_EPOCH}; - -use bytes::{buf::BufMutExt, BytesMut}; -use http::header::{HeaderValue, InvalidHeaderValue}; -use time::{PrimitiveDateTime, OffsetDateTime, offset}; - -use crate::error::ParseError; -use crate::header::IntoHeaderValue; -use crate::time_parser; - -/// A timestamp with HTTP formatting and parsing -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct HttpDate(OffsetDateTime); - -impl FromStr for HttpDate { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - match time_parser::parse_http_date(s) { - Some(t) => Ok(HttpDate(t.using_offset(offset!(UTC)))), - None => Err(ParseError::Header) - } - } -} - -impl Display for HttpDate { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.0.format("%a, %d %b %Y %H:%M:%S GMT"), f) - } -} - -impl From for HttpDate { - fn from(dt: OffsetDateTime) -> HttpDate { - HttpDate(dt) - } -} - -impl From for HttpDate { - fn from(sys: SystemTime) -> HttpDate { - HttpDate(PrimitiveDateTime::from(sys).using_offset(offset!(UTC))) - } -} - -impl IntoHeaderValue for HttpDate { - type Error = InvalidHeaderValue; - - fn try_into(self) -> Result { - let mut wrt = BytesMut::with_capacity(29).writer(); - write!(wrt, "{}", self.0.to_offset(offset!(UTC)).format("%a, %d %b %Y %H:%M:%S GMT")).unwrap(); - HeaderValue::from_maybe_shared(wrt.get_mut().split().freeze()) - } -} - -impl From for SystemTime { - fn from(date: HttpDate) -> SystemTime { - let dt = date.0; - let epoch = OffsetDateTime::unix_epoch(); - - UNIX_EPOCH + (dt - epoch) - } -} - -#[cfg(test)] -mod tests { - use super::HttpDate; - use time::{PrimitiveDateTime, date, time, offset}; - - #[test] - fn test_date() { - let nov_07 = HttpDate(PrimitiveDateTime::new( - date!(1994-11-07), - time!(8:48:37) - ).using_offset(offset!(UTC))); - - assert_eq!( - "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), - nov_07 - ); - assert_eq!( - "Sunday, 07-Nov-94 08:48:37 GMT" - .parse::() - .unwrap(), - nov_07 - ); - assert_eq!( - "Sun Nov 7 08:48:37 1994".parse::().unwrap(), - nov_07 - ); - assert!("this-is-no-date".parse::().is_err()); - } -} diff --git a/actix-http/src/header/shared/mod.rs b/actix-http/src/header/shared/mod.rs deleted file mode 100644 index f2bc91634..000000000 --- a/actix-http/src/header/shared/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Copied for `hyper::header::shared`; - -pub use self::charset::Charset; -pub use self::encoding::Encoding; -pub use self::entity::EntityTag; -pub use self::httpdate::HttpDate; -pub use self::quality_item::{q, qitem, Quality, QualityItem}; -pub use language_tags::LanguageTag; - -mod charset; -mod encoding; -mod entity; -mod httpdate; -mod quality_item; diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs deleted file mode 100644 index 98230dec1..000000000 --- a/actix-http/src/header/shared/quality_item.rs +++ /dev/null @@ -1,291 +0,0 @@ -use std::{cmp, fmt, str}; - -use self::internal::IntoQuality; - -/// Represents a quality used in quality values. -/// -/// Can be created with the `q` function. -/// -/// # Implementation notes -/// -/// The quality value is defined as a number between 0 and 1 with three decimal -/// places. This means there are 1001 possible values. Since floating point -/// numbers are not exact and the smallest floating point data type (`f32`) -/// consumes four bytes, hyper uses an `u16` value to store the -/// quality internally. For performance reasons you may set quality directly to -/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality -/// `q=0.532`. -/// -/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) -/// gives more information on quality values in HTTP header fields. -#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct Quality(u16); - -impl Default for Quality { - fn default() -> Quality { - Quality(1000) - } -} - -/// Represents an item with a quality value as defined in -/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). -#[derive(Clone, PartialEq, Debug)] -pub struct QualityItem { - /// The actual contents of the field. - pub item: T, - /// The quality (client or server preference) for the value. - pub quality: Quality, -} - -impl QualityItem { - /// Creates a new `QualityItem` from an item and a quality. - /// The item can be of any type. - /// The quality should be a value in the range [0, 1]. - pub fn new(item: T, quality: Quality) -> QualityItem { - QualityItem { item, quality } - } -} - -impl cmp::PartialOrd for QualityItem { - fn partial_cmp(&self, other: &QualityItem) -> Option { - self.quality.partial_cmp(&other.quality) - } -} - -impl fmt::Display for QualityItem { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.item, f)?; - match self.quality.0 { - 1000 => Ok(()), - 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')), - } - } -} - -impl str::FromStr for QualityItem { - type Err = crate::error::ParseError; - - fn from_str(s: &str) -> Result, crate::error::ParseError> { - if !s.is_ascii() { - return Err(crate::error::ParseError::Header); - } - // Set defaults used if parsing fails. - let mut raw_item = s; - let mut quality = 1f32; - - let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); - if parts.len() == 2 { - if parts[0].len() < 2 { - return Err(crate::error::ParseError::Header); - } - let start = &parts[0][0..2]; - if start == "q=" || start == "Q=" { - let q_part = &parts[0][2..parts[0].len()]; - if q_part.len() > 5 { - return Err(crate::error::ParseError::Header); - } - match q_part.parse::() { - Ok(q_value) => { - if 0f32 <= q_value && q_value <= 1f32 { - quality = q_value; - raw_item = parts[1]; - } else { - return Err(crate::error::ParseError::Header); - } - } - Err(_) => return Err(crate::error::ParseError::Header), - } - } - } - match raw_item.parse::() { - // we already checked above that the quality is within range - Ok(item) => Ok(QualityItem::new(item, from_f32(quality))), - Err(_) => Err(crate::error::ParseError::Header), - } - } -} - -#[inline] -fn from_f32(f: f32) -> Quality { - // this function is only used internally. A check that `f` is within range - // should be done before calling this method. Just in case, this - // debug_assert should catch if we were forgetful - debug_assert!( - f >= 0f32 && f <= 1f32, - "q value must be between 0.0 and 1.0" - ); - Quality((f * 1000f32) as u16) -} - -/// Convenience function to wrap a value in a `QualityItem` -/// Sets `q` to the default 1.0 -pub fn qitem(item: T) -> QualityItem { - QualityItem::new(item, Default::default()) -} - -/// Convenience function to create a `Quality` from a float or integer. -/// -/// Implemented for `u16` and `f32`. Panics if value is out of range. -pub fn q(val: T) -> Quality { - val.into_quality() -} - -mod internal { - use super::Quality; - - // TryFrom is probably better, but it's not stable. For now, we want to - // keep the functionality of the `q` function, while allowing it to be - // generic over `f32` and `u16`. - // - // `q` would panic before, so keep that behavior. `TryFrom` can be - // introduced later for a non-panicking conversion. - - pub trait IntoQuality: Sealed + Sized { - fn into_quality(self) -> Quality; - } - - impl IntoQuality for f32 { - fn into_quality(self) -> Quality { - assert!( - self >= 0f32 && self <= 1f32, - "float must be between 0.0 and 1.0" - ); - super::from_f32(self) - } - } - - impl IntoQuality for u16 { - fn into_quality(self) -> Quality { - assert!(self <= 1000, "u16 must be between 0 and 1000"); - Quality(self) - } - } - - pub trait Sealed {} - impl Sealed for u16 {} - impl Sealed for f32 {} -} - -#[cfg(test)] -mod tests { - use super::super::encoding::*; - use super::*; - - #[test] - fn test_quality_item_fmt_q_1() { - let x = qitem(Chunked); - assert_eq!(format!("{}", x), "chunked"); - } - #[test] - fn test_quality_item_fmt_q_0001() { - let x = QualityItem::new(Chunked, Quality(1)); - assert_eq!(format!("{}", x), "chunked; q=0.001"); - } - #[test] - fn test_quality_item_fmt_q_05() { - // Custom value - let x = QualityItem { - item: EncodingExt("identity".to_owned()), - quality: Quality(500), - }; - assert_eq!(format!("{}", x), "identity; q=0.5"); - } - - #[test] - fn test_quality_item_fmt_q_0() { - // Custom value - let x = QualityItem { - item: EncodingExt("identity".to_owned()), - quality: Quality(0), - }; - assert_eq!(x.to_string(), "identity; q=0"); - } - - #[test] - fn test_quality_item_from_str1() { - let x: Result, _> = "chunked".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Chunked, - quality: Quality(1000), - } - ); - } - #[test] - fn test_quality_item_from_str2() { - let x: Result, _> = "chunked; q=1".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Chunked, - quality: Quality(1000), - } - ); - } - #[test] - fn test_quality_item_from_str3() { - let x: Result, _> = "gzip; q=0.5".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Gzip, - quality: Quality(500), - } - ); - } - #[test] - fn test_quality_item_from_str4() { - let x: Result, _> = "gzip; q=0.273".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Gzip, - quality: Quality(273), - } - ); - } - #[test] - fn test_quality_item_from_str5() { - let x: Result, _> = "gzip; q=0.2739999".parse(); - assert!(x.is_err()); - } - #[test] - fn test_quality_item_from_str6() { - let x: Result, _> = "gzip; q=2".parse(); - assert!(x.is_err()); - } - #[test] - fn test_quality_item_ordering() { - let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); - let y: QualityItem = "gzip; q=0.273".parse().ok().unwrap(); - let comparision_result: bool = x.gt(&y); - assert!(comparision_result) - } - - #[test] - fn test_quality() { - assert_eq!(q(0.5), Quality(500)); - } - - #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] - fn test_quality_invalid() { - q(-1.0); - } - - #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] - fn test_quality_invalid2() { - q(2.0); - } - - #[test] - fn test_fuzzing_bugs() { - assert!("99999;".parse::>().is_err()); - assert!("\x0d;;;=\u{d6aa}==".parse::>().is_err()) - } -} diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs deleted file mode 100644 index 58ebff61f..000000000 --- a/actix-http/src/helpers.rs +++ /dev/null @@ -1,235 +0,0 @@ -use std::{io, mem, ptr, slice}; - -use bytes::{BufMut, BytesMut}; -use http::Version; - -use crate::extensions::Extensions; - -const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ - 2021222324252627282930313233343536373839\ - 4041424344454647484950515253545556575859\ - 6061626364656667686970717273747576777879\ - 8081828384858687888990919293949596979899"; - -pub(crate) const STATUS_LINE_BUF_SIZE: usize = 13; - -pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) { - let mut buf: [u8; STATUS_LINE_BUF_SIZE] = [ - b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1', b' ', b' ', b' ', b' ', b' ', - ]; - match version { - Version::HTTP_2 => buf[5] = b'2', - Version::HTTP_10 => buf[7] = b'0', - Version::HTTP_09 => { - buf[5] = b'0'; - buf[7] = b'9'; - } - _ => (), - } - - let mut curr: isize = 12; - let buf_ptr = buf.as_mut_ptr(); - let lut_ptr = DEC_DIGITS_LUT.as_ptr(); - let four = n > 999; - - // decode 2 more chars, if > 2 chars - let d1 = (n % 100) << 1; - n /= 100; - curr -= 2; - unsafe { - ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), 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 as isize), - buf_ptr.offset(curr), - 2, - ); - } - } - - bytes.put_slice(&buf); - if four { - bytes.put_u8(b' '); - } -} - -/// 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, - )); - } -} - -pub(crate) struct Writer<'a>(pub &'a mut BytesMut); - -impl<'a> io::Write for Writer<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub(crate) trait DataFactory { - fn set(&self, ext: &mut Extensions); -} - -pub(crate) struct Data(pub(crate) T); - -impl DataFactory for Data { - fn set(&self, ext: &mut Extensions) { - ext.insert(self.0.clone()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_write_content_length() { - let mut bytes = BytesMut::new(); - bytes.reserve(50); - write_content_length(0, &mut bytes); - assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 0\r\n"[..]); - bytes.reserve(50); - write_content_length(9, &mut bytes); - assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 9\r\n"[..]); - bytes.reserve(50); - write_content_length(10, &mut bytes); - assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 10\r\n"[..]); - bytes.reserve(50); - write_content_length(99, &mut bytes); - assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 99\r\n"[..]); - bytes.reserve(50); - write_content_length(100, &mut bytes); - assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 100\r\n"[..]); - bytes.reserve(50); - write_content_length(101, &mut bytes); - assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 101\r\n"[..]); - bytes.reserve(50); - write_content_length(998, &mut bytes); - assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 998\r\n"[..]); - bytes.reserve(50); - write_content_length(1000, &mut bytes); - assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 1000\r\n"[..]); - bytes.reserve(50); - write_content_length(1001, &mut bytes); - assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 1001\r\n"[..]); - bytes.reserve(50); - write_content_length(5909, &mut bytes); - assert_eq!(bytes.split().freeze(), b"\r\ncontent-length: 5909\r\n"[..]); - } -} diff --git a/actix-http/src/httpcodes.rs b/actix-http/src/httpcodes.rs deleted file mode 100644 index 0c7f23fc8..000000000 --- a/actix-http/src/httpcodes.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! Basic http responses -#![allow(non_upper_case_globals)] -use http::StatusCode; - -use crate::response::{Response, ResponseBuilder}; - -macro_rules! STATIC_RESP { - ($name:ident, $status:expr) => { - #[allow(non_snake_case, missing_docs)] - pub fn $name() -> ResponseBuilder { - ResponseBuilder::new($status) - } - }; -} - -impl Response { - STATIC_RESP!(Ok, StatusCode::OK); - STATIC_RESP!(Created, StatusCode::CREATED); - STATIC_RESP!(Accepted, StatusCode::ACCEPTED); - STATIC_RESP!( - NonAuthoritativeInformation, - StatusCode::NON_AUTHORITATIVE_INFORMATION - ); - - STATIC_RESP!(NoContent, StatusCode::NO_CONTENT); - STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT); - STATIC_RESP!(PartialContent, StatusCode::PARTIAL_CONTENT); - STATIC_RESP!(MultiStatus, StatusCode::MULTI_STATUS); - STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED); - - STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); - STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); - STATIC_RESP!(Found, StatusCode::FOUND); - STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER); - STATIC_RESP!(NotModified, StatusCode::NOT_MODIFIED); - STATIC_RESP!(UseProxy, StatusCode::USE_PROXY); - STATIC_RESP!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT); - STATIC_RESP!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT); - - STATIC_RESP!(BadRequest, StatusCode::BAD_REQUEST); - STATIC_RESP!(NotFound, StatusCode::NOT_FOUND); - STATIC_RESP!(Unauthorized, StatusCode::UNAUTHORIZED); - STATIC_RESP!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); - STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN); - STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); - STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); - STATIC_RESP!( - ProxyAuthenticationRequired, - StatusCode::PROXY_AUTHENTICATION_REQUIRED - ); - STATIC_RESP!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); - STATIC_RESP!(Conflict, StatusCode::CONFLICT); - STATIC_RESP!(Gone, StatusCode::GONE); - STATIC_RESP!(LengthRequired, StatusCode::LENGTH_REQUIRED); - STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); - STATIC_RESP!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED); - STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); - STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); - STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); - STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); - STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); - STATIC_RESP!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); - STATIC_RESP!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); - - STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); - STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED); - STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); - STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); - STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); - STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); - STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); - STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); - STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); -} - -#[cfg(test)] -mod tests { - use crate::body::Body; - use crate::response::Response; - use http::StatusCode; - - #[test] - fn test_build() { - let resp = Response::Ok().body(Body::Empty); - assert_eq!(resp.status(), StatusCode::OK); - } -} diff --git a/actix-http/src/httpmessage.rs b/actix-http/src/httpmessage.rs deleted file mode 100644 index e1c4136b0..000000000 --- a/actix-http/src/httpmessage.rs +++ /dev/null @@ -1,261 +0,0 @@ -use std::cell::{Ref, RefMut}; -use std::str; - -use encoding_rs::{Encoding, UTF_8}; -use http::header; -use mime::Mime; - -use crate::cookie::Cookie; -use crate::error::{ContentTypeError, CookieParseError, ParseError}; -use crate::extensions::Extensions; -use crate::header::{Header, HeaderMap}; -use crate::payload::Payload; - -struct Cookies(Vec>); - -/// Trait that implements general purpose operations on http messages -pub trait HttpMessage: Sized { - /// Type of message payload stream - type Stream; - - /// Read the message headers. - fn headers(&self) -> &HeaderMap; - - /// Message payload stream - fn take_payload(&mut self) -> Payload; - - /// Request's extensions container - fn extensions(&self) -> Ref<'_, Extensions>; - - /// Mutable reference to a the request's extensions container - fn extensions_mut(&self) -> RefMut<'_, Extensions>; - - #[doc(hidden)] - /// Get a header - fn get_header(&self) -> Option - where - Self: Sized, - { - if self.headers().contains_key(H::name()) { - H::parse(self).ok() - } else { - None - } - } - - /// Read the request content type. If request does not contain - /// *Content-Type* header, empty str get returned. - fn content_type(&self) -> &str { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return content_type.split(';').next().unwrap().trim(); - } - } - "" - } - - /// Get content type encoding - /// - /// UTF-8 is used by default, If request charset is not set. - fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> { - if let Some(mime_type) = self.mime_type()? { - if let Some(charset) = mime_type.get_param("charset") { - if let Some(enc) = - Encoding::for_label_no_replacement(charset.as_str().as_bytes()) - { - Ok(enc) - } else { - Err(ContentTypeError::UnknownEncoding) - } - } else { - Ok(UTF_8) - } - } else { - Ok(UTF_8) - } - } - - /// Convert the request content type to a known mime type. - fn mime_type(&self) -> Result, ContentTypeError> { - if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - return match content_type.parse() { - Ok(mt) => Ok(Some(mt)), - Err(_) => Err(ContentTypeError::ParseError), - }; - } else { - return Err(ContentTypeError::ParseError); - } - } - Ok(None) - } - - /// Check if request has chunked transfer encoding - fn chunked(&self) -> Result { - if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) { - if let Ok(s) = encodings.to_str() { - Ok(s.to_lowercase().contains("chunked")) - } else { - Err(ParseError::Header) - } - } else { - Ok(false) - } - } - - /// Load request cookies. - #[inline] - fn cookies(&self) -> Result>>, CookieParseError> { - if self.extensions().get::().is_none() { - let mut cookies = Vec::new(); - for hdr in self.headers().get_all(header::COOKIE) { - let s = - str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; - for cookie_str in s.split(';').map(|s| s.trim()) { - if !cookie_str.is_empty() { - cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); - } - } - } - self.extensions_mut().insert(Cookies(cookies)); - } - Ok(Ref::map(self.extensions(), |ext| { - &ext.get::().unwrap().0 - })) - } - - /// Return request cookie. - fn cookie(&self, name: &str) -> Option> { - if let Ok(cookies) = self.cookies() { - for cookie in cookies.iter() { - if cookie.name() == name { - return Some(cookie.to_owned()); - } - } - } - None - } -} - -impl<'a, T> HttpMessage for &'a mut T -where - T: HttpMessage, -{ - type Stream = T::Stream; - - fn headers(&self) -> &HeaderMap { - (**self).headers() - } - - /// Message payload stream - fn take_payload(&mut self) -> Payload { - (**self).take_payload() - } - - /// Request's extensions container - fn extensions(&self) -> Ref<'_, Extensions> { - (**self).extensions() - } - - /// Mutable reference to a the request's extensions container - fn extensions_mut(&self) -> RefMut<'_, Extensions> { - (**self).extensions_mut() - } -} - -#[cfg(test)] -mod tests { - use bytes::Bytes; - use encoding_rs::ISO_8859_2; - use mime; - - use super::*; - use crate::test::TestRequest; - - #[test] - fn test_content_type() { - let req = TestRequest::with_header("content-type", "text/plain").finish(); - assert_eq!(req.content_type(), "text/plain"); - let req = - TestRequest::with_header("content-type", "application/json; charset=utf=8") - .finish(); - assert_eq!(req.content_type(), "application/json"); - let req = TestRequest::default().finish(); - assert_eq!(req.content_type(), ""); - } - - #[test] - fn test_mime_type() { - let req = TestRequest::with_header("content-type", "application/json").finish(); - assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON)); - let req = TestRequest::default().finish(); - assert_eq!(req.mime_type().unwrap(), None); - let req = - TestRequest::with_header("content-type", "application/json; charset=utf-8") - .finish(); - let mt = req.mime_type().unwrap().unwrap(); - assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8)); - assert_eq!(mt.type_(), mime::APPLICATION); - assert_eq!(mt.subtype(), mime::JSON); - } - - #[test] - fn test_mime_type_error() { - let req = TestRequest::with_header( - "content-type", - "applicationadfadsfasdflknadsfklnadsfjson", - ) - .finish(); - assert_eq!(Err(ContentTypeError::ParseError), req.mime_type()); - } - - #[test] - fn test_encoding() { - let req = TestRequest::default().finish(); - assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - - let req = TestRequest::with_header("content-type", "application/json").finish(); - assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - - let req = TestRequest::with_header( - "content-type", - "application/json; charset=ISO-8859-2", - ) - .finish(); - assert_eq!(ISO_8859_2, req.encoding().unwrap()); - } - - #[test] - fn test_encoding_error() { - let req = TestRequest::with_header("content-type", "applicatjson").finish(); - assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); - - let req = TestRequest::with_header( - "content-type", - "application/json; charset=kkkttktk", - ) - .finish(); - assert_eq!( - Some(ContentTypeError::UnknownEncoding), - req.encoding().err() - ); - } - - #[test] - fn test_chunked() { - let req = TestRequest::default().finish(); - assert!(!req.chunked().unwrap()); - - let req = - TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); - assert!(req.chunked().unwrap()); - - let req = TestRequest::default() - .header( - header::TRANSFER_ENCODING, - Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"), - ) - .finish(); - assert!(req.chunked().is_err()); - } -} diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs deleted file mode 100644 index a5ae4b447..000000000 --- a/actix-http/src/lib.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! Basic http primitives for actix-net framework. -#![deny(rust_2018_idioms, warnings)] -#![allow( - clippy::type_complexity, - clippy::too_many_arguments, - clippy::new_without_default, - clippy::borrow_interior_mutable_const -)] - -#[macro_use] -extern crate log; - -pub mod body; -mod builder; -pub mod client; -mod cloneable; -mod config; -#[cfg(feature = "compress")] -pub mod encoding; -mod extensions; -mod header; -mod helpers; -mod httpcodes; -pub mod httpmessage; -mod message; -mod payload; -mod request; -mod response; -mod service; -mod time_parser; - -pub mod cookie; -pub mod error; -pub mod h1; -pub mod h2; -pub mod test; -pub mod ws; - -pub use self::builder::HttpServiceBuilder; -pub use self::config::{KeepAlive, ServiceConfig}; -pub use self::error::{Error, ResponseError, Result}; -pub use self::extensions::Extensions; -pub use self::httpmessage::HttpMessage; -pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead}; -pub use self::payload::{Payload, PayloadStream}; -pub use self::request::Request; -pub use self::response::{Response, ResponseBuilder}; -pub use self::service::HttpService; - -pub mod http { - //! Various HTTP related types - - // re-exports - pub use http::header::{HeaderName, HeaderValue}; - pub use http::uri::PathAndQuery; - pub use http::{uri, Error, Uri}; - pub use http::{Method, StatusCode, Version}; - - pub use crate::cookie::{Cookie, CookieBuilder}; - pub use crate::header::HeaderMap; - - /// Various http headers - pub mod header { - pub use crate::header::*; - } - pub use crate::header::ContentEncoding; - pub use crate::message::ConnectionType; -} - -/// Http protocol -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub enum Protocol { - Http1, - Http2, -} diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs deleted file mode 100644 index d005ad04a..000000000 --- a/actix-http/src/message.rs +++ /dev/null @@ -1,495 +0,0 @@ -use std::cell::{Ref, RefCell, RefMut}; -use std::net; -use std::rc::Rc; - -use bitflags::bitflags; -use copyless::BoxHelper; - -use crate::extensions::Extensions; -use crate::header::HeaderMap; -use crate::http::{header, Method, StatusCode, Uri, Version}; - -/// Represents various types of connection -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ConnectionType { - /// Close connection after response - Close, - /// Keep connection alive after response - KeepAlive, - /// Connection is upgraded to different type - Upgrade, -} - -bitflags! { - pub(crate) struct Flags: u8 { - const CLOSE = 0b0000_0001; - const KEEP_ALIVE = 0b0000_0010; - const UPGRADE = 0b0000_0100; - const EXPECT = 0b0000_1000; - const NO_CHUNKING = 0b0001_0000; - const CAMEL_CASE = 0b0010_0000; - } -} - -#[doc(hidden)] -pub trait Head: Default + 'static { - fn clear(&mut self); - - fn pool() -> &'static MessagePool; -} - -#[derive(Debug)] -pub struct RequestHead { - pub uri: Uri, - pub method: Method, - pub version: Version, - pub headers: HeaderMap, - pub extensions: RefCell, - pub peer_addr: Option, - flags: Flags, -} - -impl Default for RequestHead { - fn default() -> RequestHead { - RequestHead { - uri: Uri::default(), - method: Method::default(), - version: Version::HTTP_11, - headers: HeaderMap::with_capacity(16), - flags: Flags::empty(), - peer_addr: None, - extensions: RefCell::new(Extensions::new()), - } - } -} - -impl Head for RequestHead { - fn clear(&mut self) { - self.flags = Flags::empty(); - self.headers.clear(); - self.extensions.borrow_mut().clear(); - } - - fn pool() -> &'static MessagePool { - REQUEST_POOL.with(|p| *p) - } -} - -impl RequestHead { - /// Message extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - self.extensions.borrow() - } - - /// Mutable reference to a the message's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.extensions.borrow_mut() - } - - /// Read the message headers. - pub fn headers(&self) -> &HeaderMap { - &self.headers - } - - /// Mutable reference to the message headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers - } - - /// Is to uppercase headers with Camel-Case. - /// Befault is `false` - #[inline] - pub fn camel_case_headers(&self) -> bool { - self.flags.contains(Flags::CAMEL_CASE) - } - - /// Set `true` to send headers which are uppercased with Camel-Case. - #[inline] - pub fn set_camel_case_headers(&mut self, val: bool) { - if val { - self.flags.insert(Flags::CAMEL_CASE); - } else { - self.flags.remove(Flags::CAMEL_CASE); - } - } - - #[inline] - /// Set connection type of the message - pub fn set_connection_type(&mut self, ctype: ConnectionType) { - match ctype { - ConnectionType::Close => self.flags.insert(Flags::CLOSE), - ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE), - ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE), - } - } - - #[inline] - /// Connection type - pub fn connection_type(&self) -> ConnectionType { - if self.flags.contains(Flags::CLOSE) { - ConnectionType::Close - } else if self.flags.contains(Flags::KEEP_ALIVE) { - ConnectionType::KeepAlive - } else if self.flags.contains(Flags::UPGRADE) { - ConnectionType::Upgrade - } else if self.version < Version::HTTP_11 { - ConnectionType::Close - } else { - ConnectionType::KeepAlive - } - } - - /// Connection upgrade status - pub fn upgrade(&self) -> bool { - if let Some(hdr) = self.headers().get(header::CONNECTION) { - if let Ok(s) = hdr.to_str() { - s.to_ascii_lowercase().contains("upgrade") - } else { - false - } - } else { - false - } - } - - #[inline] - /// Get response body chunking state - pub fn chunked(&self) -> bool { - !self.flags.contains(Flags::NO_CHUNKING) - } - - #[inline] - pub fn no_chunking(&mut self, val: bool) { - if val { - self.flags.insert(Flags::NO_CHUNKING); - } else { - self.flags.remove(Flags::NO_CHUNKING); - } - } - - #[inline] - /// Request contains `EXPECT` header - pub fn expect(&self) -> bool { - self.flags.contains(Flags::EXPECT) - } - - #[inline] - pub(crate) fn set_expect(&mut self) { - self.flags.insert(Flags::EXPECT); - } -} - -#[derive(Debug)] -pub enum RequestHeadType { - Owned(RequestHead), - Rc(Rc, Option), -} - -impl RequestHeadType { - pub fn extra_headers(&self) -> Option<&HeaderMap> { - match self { - RequestHeadType::Owned(_) => None, - RequestHeadType::Rc(_, headers) => headers.as_ref(), - } - } -} - -impl AsRef for RequestHeadType { - fn as_ref(&self) -> &RequestHead { - match self { - RequestHeadType::Owned(head) => &head, - RequestHeadType::Rc(head, _) => head.as_ref(), - } - } -} - -impl From for RequestHeadType { - fn from(head: RequestHead) -> Self { - RequestHeadType::Owned(head) - } -} - -#[derive(Debug)] -pub struct ResponseHead { - pub version: Version, - pub status: StatusCode, - pub headers: HeaderMap, - pub reason: Option<&'static str>, - pub(crate) extensions: RefCell, - flags: Flags, -} - -impl ResponseHead { - /// Create new instance of `ResponseHead` type - #[inline] - pub fn new(status: StatusCode) -> ResponseHead { - ResponseHead { - status, - version: Version::default(), - headers: HeaderMap::with_capacity(12), - reason: None, - flags: Flags::empty(), - extensions: RefCell::new(Extensions::new()), - } - } - - /// Message extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - self.extensions.borrow() - } - - /// Mutable reference to a the message's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.extensions.borrow_mut() - } - - #[inline] - /// Read the message headers. - pub fn headers(&self) -> &HeaderMap { - &self.headers - } - - #[inline] - /// Mutable reference to the message headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.headers - } - - #[inline] - /// Set connection type of the message - pub fn set_connection_type(&mut self, ctype: ConnectionType) { - match ctype { - ConnectionType::Close => self.flags.insert(Flags::CLOSE), - ConnectionType::KeepAlive => self.flags.insert(Flags::KEEP_ALIVE), - ConnectionType::Upgrade => self.flags.insert(Flags::UPGRADE), - } - } - - #[inline] - pub fn connection_type(&self) -> ConnectionType { - if self.flags.contains(Flags::CLOSE) { - ConnectionType::Close - } else if self.flags.contains(Flags::KEEP_ALIVE) { - ConnectionType::KeepAlive - } else if self.flags.contains(Flags::UPGRADE) { - ConnectionType::Upgrade - } else if self.version < Version::HTTP_11 { - ConnectionType::Close - } else { - ConnectionType::KeepAlive - } - } - - #[inline] - /// Check if keep-alive is enabled - pub fn keep_alive(&self) -> bool { - self.connection_type() == ConnectionType::KeepAlive - } - - #[inline] - /// Check upgrade status of this message - pub fn upgrade(&self) -> bool { - self.connection_type() == ConnectionType::Upgrade - } - - /// Get custom reason for the response - #[inline] - pub fn reason(&self) -> &str { - if let Some(reason) = self.reason { - reason - } else { - self.status - .canonical_reason() - .unwrap_or("") - } - } - - #[inline] - pub(crate) fn ctype(&self) -> Option { - if self.flags.contains(Flags::CLOSE) { - Some(ConnectionType::Close) - } else if self.flags.contains(Flags::KEEP_ALIVE) { - Some(ConnectionType::KeepAlive) - } else if self.flags.contains(Flags::UPGRADE) { - Some(ConnectionType::Upgrade) - } else { - None - } - } - - #[inline] - /// Get response body chunking state - pub fn chunked(&self) -> bool { - !self.flags.contains(Flags::NO_CHUNKING) - } - - #[inline] - /// Set no chunking for payload - pub fn no_chunking(&mut self, val: bool) { - if val { - self.flags.insert(Flags::NO_CHUNKING); - } else { - self.flags.remove(Flags::NO_CHUNKING); - } - } -} - -pub struct Message { - head: Rc, -} - -impl Message { - /// Get new message from the pool of objects - pub fn new() -> Self { - T::pool().get_message() - } -} - -impl Clone for Message { - fn clone(&self) -> Self { - Message { - head: self.head.clone(), - } - } -} - -impl std::ops::Deref for Message { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.head.as_ref() - } -} - -impl std::ops::DerefMut for Message { - fn deref_mut(&mut self) -> &mut Self::Target { - Rc::get_mut(&mut self.head).expect("Multiple copies exist") - } -} - -impl Drop for Message { - fn drop(&mut self) { - if Rc::strong_count(&self.head) == 1 { - T::pool().release(self.head.clone()); - } - } -} - -pub(crate) struct BoxedResponseHead { - head: Option>, -} - -impl BoxedResponseHead { - /// Get new message from the pool of objects - pub fn new(status: StatusCode) -> Self { - RESPONSE_POOL.with(|p| p.get_message(status)) - } - - pub(crate) fn take(&mut self) -> Self { - BoxedResponseHead { - head: self.head.take(), - } - } -} - -impl std::ops::Deref for BoxedResponseHead { - type Target = ResponseHead; - - fn deref(&self) -> &Self::Target { - self.head.as_ref().unwrap() - } -} - -impl std::ops::DerefMut for BoxedResponseHead { - fn deref_mut(&mut self) -> &mut Self::Target { - self.head.as_mut().unwrap() - } -} - -impl Drop for BoxedResponseHead { - fn drop(&mut self) { - if let Some(head) = self.head.take() { - RESPONSE_POOL.with(move |p| p.release(head)) - } - } -} - -#[doc(hidden)] -/// Request's objects pool -pub struct MessagePool(RefCell>>); - -#[doc(hidden)] -#[allow(clippy::vec_box)] -/// Request's objects pool -pub struct BoxedResponsePool(RefCell>>); - -thread_local!(static REQUEST_POOL: &'static MessagePool = MessagePool::::create()); -thread_local!(static RESPONSE_POOL: &'static BoxedResponsePool = BoxedResponsePool::create()); - -impl MessagePool { - fn create() -> &'static MessagePool { - let pool = MessagePool(RefCell::new(Vec::with_capacity(128))); - Box::leak(Box::new(pool)) - } - - /// Get message from the pool - #[inline] - fn get_message(&'static self) -> Message { - if let Some(mut msg) = self.0.borrow_mut().pop() { - if let Some(r) = Rc::get_mut(&mut msg) { - r.clear(); - } - Message { head: msg } - } else { - Message { - head: Rc::new(T::default()), - } - } - } - - #[inline] - /// Release request instance - fn release(&self, msg: Rc) { - let v = &mut self.0.borrow_mut(); - if v.len() < 128 { - v.push(msg); - } - } -} - -impl BoxedResponsePool { - fn create() -> &'static BoxedResponsePool { - let pool = BoxedResponsePool(RefCell::new(Vec::with_capacity(128))); - Box::leak(Box::new(pool)) - } - - /// Get message from the pool - #[inline] - fn get_message(&'static self, status: StatusCode) -> BoxedResponseHead { - if let Some(mut head) = self.0.borrow_mut().pop() { - head.reason = None; - head.status = status; - head.headers.clear(); - head.flags = Flags::empty(); - BoxedResponseHead { head: Some(head) } - } else { - BoxedResponseHead { - head: Some(Box::alloc().init(ResponseHead::new(status))), - } - } - } - - #[inline] - /// Release request instance - fn release(&self, msg: Box) { - let v = &mut self.0.borrow_mut(); - if v.len() < 128 { - msg.extensions.borrow_mut().clear(); - v.push(msg); - } - } -} diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs deleted file mode 100644 index 54de6ed93..000000000 --- a/actix-http/src/payload.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; - -use bytes::Bytes; -use futures_core::Stream; -use h2::RecvStream; - -use crate::error::PayloadError; - -/// Type represent boxed payload -pub type PayloadStream = Pin>>>; - -/// Type represent streaming payload -pub enum Payload { - None, - H1(crate::h1::Payload), - H2(crate::h2::Payload), - Stream(S), -} - -impl From for Payload { - fn from(v: crate::h1::Payload) -> Self { - Payload::H1(v) - } -} - -impl From for Payload { - fn from(v: crate::h2::Payload) -> Self { - Payload::H2(v) - } -} - -impl From for Payload { - fn from(v: RecvStream) -> Self { - Payload::H2(crate::h2::Payload::new(v)) - } -} - -impl From for Payload { - fn from(pl: PayloadStream) -> Self { - Payload::Stream(pl) - } -} - -impl Payload { - /// Takes current payload and replaces it with `None` value - pub fn take(&mut self) -> Payload { - std::mem::replace(self, Payload::None) - } -} - -impl Stream for Payload -where - S: Stream> + Unpin, -{ - type Item = Result; - - #[inline] - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - match self.get_mut() { - Payload::None => Poll::Ready(None), - Payload::H1(ref mut pl) => pl.readany(cx), - Payload::H2(ref mut pl) => Pin::new(pl).poll_next(cx), - Payload::Stream(ref mut pl) => Pin::new(pl).poll_next(cx), - } - } -} diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs deleted file mode 100644 index 64e302441..000000000 --- a/actix-http/src/request.rs +++ /dev/null @@ -1,209 +0,0 @@ -use std::cell::{Ref, RefMut}; -use std::{fmt, net}; - -use http::{header, Method, Uri, Version}; - -use crate::extensions::Extensions; -use crate::header::HeaderMap; -use crate::httpmessage::HttpMessage; -use crate::message::{Message, RequestHead}; -use crate::payload::{Payload, PayloadStream}; - -/// Request -pub struct Request

    { - pub(crate) payload: Payload

    , - pub(crate) head: Message, -} - -impl

    HttpMessage for Request

    { - type Stream = P; - - #[inline] - fn headers(&self) -> &HeaderMap { - &self.head().headers - } - - /// Request extensions - #[inline] - fn extensions(&self) -> Ref<'_, Extensions> { - self.head.extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.head.extensions_mut() - } - - fn take_payload(&mut self) -> Payload

    { - std::mem::replace(&mut self.payload, Payload::None) - } -} - -impl From> for Request { - fn from(head: Message) -> Self { - Request { - head, - payload: Payload::None, - } - } -} - -impl Request { - /// Create new Request instance - pub fn new() -> Request { - Request { - head: Message::new(), - payload: Payload::None, - } - } -} - -impl

    Request

    { - /// Create new Request instance - pub fn with_payload(payload: Payload

    ) -> Request

    { - Request { - payload, - head: Message::new(), - } - } - - /// Create new Request instance - pub fn replace_payload(self, payload: Payload) -> (Request, Payload

    ) { - let pl = self.payload; - ( - Request { - payload, - head: self.head, - }, - pl, - ) - } - - /// Get request's payload - pub fn payload(&mut self) -> &mut Payload

    { - &mut self.payload - } - - /// Get request's payload - pub fn take_payload(&mut self) -> Payload

    { - std::mem::replace(&mut self.payload, Payload::None) - } - - /// Split request into request head and payload - pub fn into_parts(self) -> (Message, Payload

    ) { - (self.head, self.payload) - } - - #[inline] - /// Http message part of the request - pub fn head(&self) -> &RequestHead { - &*self.head - } - - #[inline] - #[doc(hidden)] - /// Mutable reference to a http message part of the request - pub fn head_mut(&mut self) -> &mut RequestHead { - &mut *self.head - } - - /// Mutable reference to the message's headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().headers - } - - /// Request's uri. - #[inline] - pub fn uri(&self) -> &Uri { - &self.head().uri - } - - /// Mutable reference to the request's uri. - #[inline] - pub fn uri_mut(&mut self) -> &mut Uri { - &mut self.head_mut().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 - } - - /// The target path of this Request. - #[inline] - pub fn path(&self) -> &str { - self.head().uri.path() - } - - /// Check if request requires connection upgrade - #[inline] - pub fn upgrade(&self) -> bool { - if let Some(conn) = self.head().headers.get(header::CONNECTION) { - if let Ok(s) = conn.to_str() { - return s.to_lowercase().contains("upgrade"); - } - } - self.head().method == Method::CONNECT - } - - /// Peer socket address - /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. - #[inline] - pub fn peer_addr(&self) -> Option { - self.head().peer_addr - } -} - -impl

    fmt::Debug for Request

    { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!( - f, - "\nRequest {:?} {}:{}", - self.version(), - self.method(), - self.path() - )?; - if let Some(q) = self.uri().query().as_ref() { - writeln!(f, " query: ?{:?}", q)?; - } - writeln!(f, " headers:")?; - for (key, val) in self.headers() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::convert::TryFrom; - - #[test] - fn test_basics() { - let msg = Message::new(); - let mut req = Request::from(msg); - req.headers_mut().insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), - ); - assert!(req.headers().contains_key(header::CONTENT_TYPE)); - - *req.uri_mut() = Uri::try_from("/index.html?q=1").unwrap(); - assert_eq!(req.uri().path(), "/index.html"); - assert_eq!(req.uri().query(), Some("q=1")); - - let s = format!("{:?}", req); - assert!(s.contains("Request HTTP/1.1 GET:/index.html")); - } -} diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs deleted file mode 100644 index fcdcd7cdf..000000000 --- a/actix-http/src/response.rs +++ /dev/null @@ -1,1088 +0,0 @@ -//! Http response -use std::cell::{Ref, RefMut}; -use std::convert::TryFrom; -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, str}; - -use bytes::{Bytes, BytesMut}; -use futures_core::Stream; -use serde::Serialize; -use serde_json; - -use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; -use crate::cookie::{Cookie, CookieJar}; -use crate::error::Error; -use crate::extensions::Extensions; -use crate::header::{Header, IntoHeaderValue}; -use crate::http::header::{self, HeaderName, HeaderValue}; -use crate::http::{Error as HttpError, HeaderMap, StatusCode}; -use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; - -/// An HTTP Response -pub struct Response { - head: BoxedResponseHead, - body: ResponseBody, - error: Option, -} - -impl Response { - /// Create http response builder with specific status. - #[inline] - pub fn build(status: StatusCode) -> ResponseBuilder { - ResponseBuilder::new(status) - } - - /// Create http response builder - #[inline] - pub fn build_from>(source: T) -> ResponseBuilder { - source.into() - } - - /// Constructs a response - #[inline] - pub fn new(status: StatusCode) -> Response { - Response { - head: BoxedResponseHead::new(status), - body: ResponseBody::Body(Body::Empty), - error: None, - } - } - - /// Constructs an error response - #[inline] - pub fn from_error(error: Error) -> Response { - let mut resp = error.as_response_error().error_response(); - if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR { - error!("Internal Server Error: {:?}", error); - } - resp.error = Some(error); - resp - } - - /// Convert response to response with body - pub fn into_body(self) -> Response { - let b = match self.body { - ResponseBody::Body(b) => b, - ResponseBody::Other(b) => b, - }; - Response { - head: self.head, - error: self.error, - body: ResponseBody::Other(b), - } - } -} - -impl Response { - /// Constructs a response with body - #[inline] - pub fn with_body(status: StatusCode, body: B) -> Response { - Response { - head: BoxedResponseHead::new(status), - body: ResponseBody::Body(body), - error: None, - } - } - - #[inline] - /// Http message part of the response - pub fn head(&self) -> &ResponseHead { - &*self.head - } - - #[inline] - /// Mutable reference to a http message part of the response - pub fn head_mut(&mut self) -> &mut ResponseHead { - &mut *self.head - } - - /// The source `error` for this response - #[inline] - pub fn error(&self) -> Option<&Error> { - self.error.as_ref() - } - - /// Get the response status code - #[inline] - pub fn status(&self) -> StatusCode { - self.head.status - } - - /// Set the `StatusCode` for this response - #[inline] - pub fn status_mut(&mut self) -> &mut StatusCode { - &mut self.head.status - } - - /// Get the headers from the response - #[inline] - pub fn headers(&self) -> &HeaderMap { - &self.head.headers - } - - /// Get a mutable reference to the headers - #[inline] - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head.headers - } - - /// Get an iterator for the cookies set by this response - #[inline] - pub fn cookies(&self) -> CookieIter<'_> { - CookieIter { - iter: self.head.headers.get_all(header::SET_COOKIE), - } - } - - /// Add a cookie to this response - #[inline] - pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> { - let h = &mut self.head.headers; - HeaderValue::from_str(&cookie.to_string()) - .map(|c| { - h.append(header::SET_COOKIE, c); - }) - .map_err(|e| e.into()) - } - - /// Remove all cookies with the given name from this response. Returns - /// the number of cookies removed. - #[inline] - pub fn del_cookie(&mut self, name: &str) -> usize { - let h = &mut self.head.headers; - let vals: Vec = h - .get_all(header::SET_COOKIE) - .map(|v| v.to_owned()) - .collect(); - h.remove(header::SET_COOKIE); - - let mut count: usize = 0; - for v in vals { - if let Ok(s) = v.to_str() { - if let Ok(c) = Cookie::parse_encoded(s) { - if c.name() == name { - count += 1; - continue; - } - } - } - h.append(header::SET_COOKIE, v); - } - count - } - - /// Connection upgrade status - #[inline] - pub fn upgrade(&self) -> bool { - self.head.upgrade() - } - - /// Keep-alive status for this connection - pub fn keep_alive(&self) -> bool { - self.head.keep_alive() - } - - /// Responses extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - self.head.extensions.borrow() - } - - /// Mutable reference to a the response's extensions - #[inline] - pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { - self.head.extensions.borrow_mut() - } - - /// Get body of this response - #[inline] - pub fn body(&self) -> &ResponseBody { - &self.body - } - - /// Set a body - pub fn set_body(self, body: B2) -> Response { - Response { - head: self.head, - body: ResponseBody::Body(body), - error: None, - } - } - - /// Split response and body - pub fn into_parts(self) -> (Response<()>, ResponseBody) { - ( - Response { - head: self.head, - body: ResponseBody::Body(()), - error: self.error, - }, - self.body, - ) - } - - /// Drop request's body - pub fn drop_body(self) -> Response<()> { - Response { - head: self.head, - body: ResponseBody::Body(()), - error: None, - } - } - - /// Set a body and return previous body value - pub(crate) fn replace_body(self, body: B2) -> (Response, ResponseBody) { - ( - Response { - head: self.head, - body: ResponseBody::Body(body), - error: self.error, - }, - self.body, - ) - } - - /// Set a body and return previous body value - pub fn map_body(mut self, f: F) -> Response - where - F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, - { - let body = f(&mut self.head, self.body); - - Response { - body, - head: self.head, - error: self.error, - } - } - - /// Extract response body - pub fn take_body(&mut self) -> ResponseBody { - self.body.take_body() - } -} - -impl fmt::Debug for Response { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let res = writeln!( - f, - "\nResponse {:?} {}{}", - self.head.version, - self.head.status, - self.head.reason.unwrap_or(""), - ); - let _ = writeln!(f, " headers:"); - for (key, val) in self.head.headers.iter() { - let _ = writeln!(f, " {:?}: {:?}", key, val); - } - let _ = writeln!(f, " body: {:?}", self.body.size()); - res - } -} - -impl Future for Response { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - Poll::Ready(Ok(Response { - head: self.head.take(), - body: self.body.take_body(), - error: self.error.take(), - })) - } -} - -pub struct CookieIter<'a> { - iter: header::GetAll<'a>, -} - -impl<'a> Iterator for CookieIter<'a> { - type Item = Cookie<'a>; - - #[inline] - fn next(&mut self) -> Option> { - for v in self.iter.by_ref() { - if let Ok(c) = Cookie::parse_encoded(v.to_str().ok()?) { - return Some(c); - } - } - None - } -} - -/// An HTTP response builder -/// -/// This type can be used to construct an instance of `Response` through a -/// builder-like pattern. -pub struct ResponseBuilder { - head: Option, - err: Option, - cookies: Option, -} - -impl ResponseBuilder { - #[inline] - /// Create response builder - pub fn new(status: StatusCode) -> Self { - ResponseBuilder { - head: Some(BoxedResponseHead::new(status)), - err: None, - cookies: None, - } - } - - /// Set HTTP status code of this response. - #[inline] - pub fn status(&mut self, status: StatusCode) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.status = status; - } - self - } - - /// Set a header. - /// - /// ```rust - /// use actix_http::{http, Request, Response, Result}; - /// - /// fn index(req: Request) -> Result { - /// Ok(Response::Ok() - /// .set(http::header::IfModifiedSince( - /// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?, - /// )) - /// .finish()) - /// } - /// ``` - #[doc(hidden)] - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - match hdr.try_into() { - Ok(value) => { - parts.headers.append(H::name(), value); - } - Err(e) => self.err = Some(e.into()), - } - } - self - } - - /// Append a header to existing headers. - /// - /// ```rust - /// use actix_http::{http, Request, Response}; - /// - /// fn index(req: Request) -> Response { - /// Response::Ok() - /// .header("X-TEST", "value") - /// .header(http::header::CONTENT_TYPE, "application/json") - /// .finish() - /// } - /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.append(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set a header. - /// - /// ```rust - /// use actix_http::{http, Request, Response}; - /// - /// fn index(req: Request) -> Response { - /// Response::Ok() - /// .set_header("X-TEST", "value") - /// .set_header(http::header::CONTENT_TYPE, "application/json") - /// .finish() - /// } - /// ``` - pub fn set_header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set the custom reason for the response. - #[inline] - pub fn reason(&mut self, reason: &'static str) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.reason = Some(reason); - } - self - } - - /// Set connection type to KeepAlive - #[inline] - pub fn keep_alive(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::KeepAlive); - } - self - } - - /// Set connection type to Upgrade - #[inline] - pub fn upgrade(&mut self, value: V) -> &mut Self - where - V: IntoHeaderValue, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::Upgrade); - } - self.set_header(header::UPGRADE, value) - } - - /// Force close connection, even if it is marked as keep-alive - #[inline] - pub fn force_close(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.set_connection_type(ConnectionType::Close); - } - self - } - - /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. - #[inline] - pub fn no_chunking(&mut self) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - parts.no_chunking(true); - } - self - } - - /// Set response content type - #[inline] - pub fn content_type(&mut self, value: V) -> &mut Self - where - HeaderValue: TryFrom, - >::Error: Into, - { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderValue::try_from(value) { - Ok(value) => { - parts.headers.insert(header::CONTENT_TYPE, value); - } - Err(e) => self.err = Some(e.into()), - }; - } - self - } - - /// Set content length - #[inline] - pub fn content_length(&mut self, len: u64) -> &mut Self { - self.header(header::CONTENT_LENGTH, len) - } - - /// Set a cookie - /// - /// ```rust - /// use actix_http::{http, Request, Response}; - /// - /// fn index(req: Request) -> Response { - /// Response::Ok() - /// .cookie( - /// http::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .finish() - /// } - /// ``` - pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { - if self.cookies.is_none() { - let mut jar = CookieJar::new(); - jar.add(cookie.into_owned()); - self.cookies = Some(jar) - } else { - self.cookies.as_mut().unwrap().add(cookie.into_owned()); - } - self - } - - /// Remove cookie - /// - /// ```rust - /// use actix_http::{http, Request, Response, HttpMessage}; - /// - /// fn index(req: Request) -> Response { - /// let mut builder = Response::Ok(); - /// - /// if let Some(ref cookie) = req.cookie("name") { - /// builder.del_cookie(cookie); - /// } - /// - /// builder.finish() - /// } - /// ``` - pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { - if self.cookies.is_none() { - self.cookies = Some(CookieJar::new()) - } - let jar = self.cookies.as_mut().unwrap(); - let cookie = cookie.clone().into_owned(); - jar.add_original(cookie.clone()); - jar.remove(cookie); - self - } - - /// This method calls provided closure with builder reference if value is - /// true. - pub fn if_true(&mut self, value: bool, f: F) -> &mut Self - where - F: FnOnce(&mut ResponseBuilder), - { - if value { - f(self); - } - self - } - - /// This method calls provided closure with builder reference if value is - /// Some. - pub fn if_some(&mut self, value: Option, f: F) -> &mut Self - where - F: FnOnce(T, &mut ResponseBuilder), - { - if let Some(val) = value { - f(val, self); - } - self - } - - /// Responses extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - let head = self.head.as_ref().expect("cannot reuse response builder"); - head.extensions.borrow() - } - - /// Mutable reference to a the response's extensions - #[inline] - pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> { - let head = self.head.as_ref().expect("cannot reuse response builder"); - head.extensions.borrow_mut() - } - - #[inline] - /// Set a body and generate `Response`. - /// - /// `ResponseBuilder` can not be used after this call. - pub fn body>(&mut self, body: B) -> Response { - self.message_body(body.into()) - } - - /// Set a body and generate `Response`. - /// - /// `ResponseBuilder` can not be used after this call. - pub fn message_body(&mut self, body: B) -> Response { - if let Some(e) = self.err.take() { - return Response::from(Error::from(e)).into_body(); - } - - let mut response = self.head.take().expect("cannot reuse response builder"); - - if let Some(ref jar) = self.cookies { - for cookie in jar.delta() { - match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => response.headers.append(header::SET_COOKIE, val), - Err(e) => return Response::from(Error::from(e)).into_body(), - }; - } - } - - Response { - head: response, - body: ResponseBody::Body(body), - error: None, - } - } - - #[inline] - /// Set a streaming body and generate `Response`. - /// - /// `ResponseBuilder` can not be used after this call. - pub fn streaming(&mut self, stream: S) -> Response - where - S: Stream> + 'static, - E: Into + 'static, - { - self.body(Body::from_message(BodyStream::new(stream))) - } - - #[inline] - /// Set a json body and generate `Response` - /// - /// `ResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Response { - self.json2(&value) - } - - /// Set a json body and generate `Response` - /// - /// `ResponseBuilder` can not be used after this call. - pub fn json2(&mut self, value: &T) -> Response { - match serde_json::to_string(value) { - Ok(body) => { - let contains = if let Some(parts) = parts(&mut self.head, &self.err) { - parts.headers.contains_key(header::CONTENT_TYPE) - } else { - true - }; - if !contains { - self.header(header::CONTENT_TYPE, "application/json"); - } - - self.body(Body::from(body)) - } - Err(e) => Error::from(e).into(), - } - } - - #[inline] - /// Set an empty body and generate `Response` - /// - /// `ResponseBuilder` can not be used after this call. - pub fn finish(&mut self) -> Response { - self.body(Body::Empty) - } - - /// This method construct new `ResponseBuilder` - pub fn take(&mut self) -> ResponseBuilder { - ResponseBuilder { - head: self.head.take(), - err: self.err.take(), - cookies: self.cookies.take(), - } - } -} - -#[inline] -fn parts<'a>( - parts: &'a mut Option, - err: &Option, -) -> Option<&'a mut ResponseHead> { - if err.is_some() { - return None; - } - parts.as_mut().map(|r| &mut **r) -} - -/// Convert `Response` to a `ResponseBuilder`. Body get dropped. -impl From> for ResponseBuilder { - fn from(res: Response) -> ResponseBuilder { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - for c in res.cookies() { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - - ResponseBuilder { - head: Some(res.head), - err: None, - cookies: jar, - } - } -} - -/// Convert `ResponseHead` to a `ResponseBuilder` -impl<'a> From<&'a ResponseHead> for ResponseBuilder { - fn from(head: &'a ResponseHead) -> ResponseBuilder { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - - let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE), - }; - for c in cookies { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - - let mut msg = BoxedResponseHead::new(head.status); - msg.version = head.version; - msg.reason = head.reason; - for (k, v) in &head.headers { - msg.headers.append(k.clone(), v.clone()); - } - msg.no_chunking(!head.chunked()); - - ResponseBuilder { - head: Some(msg), - err: None, - cookies: jar, - } - } -} - -impl Future for ResponseBuilder { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - Poll::Ready(Ok(self.finish())) - } -} - -impl fmt::Debug for ResponseBuilder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let head = self.head.as_ref().unwrap(); - - let res = writeln!( - f, - "\nResponseBuilder {:?} {}{}", - head.version, - head.status, - head.reason.unwrap_or(""), - ); - let _ = writeln!(f, " headers:"); - for (key, val) in head.headers.iter() { - let _ = writeln!(f, " {:?}: {:?}", key, val); - } - res - } -} - -/// Helper converters -impl, E: Into> From> for Response { - fn from(res: Result) -> Self { - match res { - Ok(val) => val.into(), - Err(err) => err.into().into(), - } - } -} - -impl From for Response { - fn from(mut builder: ResponseBuilder) -> Self { - builder.finish() - } -} - -impl From<&'static str> for Response { - fn from(val: &'static str) -> Self { - Response::Ok() - .content_type("text/plain; charset=utf-8") - .body(val) - } -} - -impl From<&'static [u8]> for Response { - fn from(val: &'static [u8]) -> Self { - Response::Ok() - .content_type("application/octet-stream") - .body(val) - } -} - -impl From for Response { - fn from(val: String) -> Self { - Response::Ok() - .content_type("text/plain; charset=utf-8") - .body(val) - } -} - -impl<'a> From<&'a String> for Response { - fn from(val: &'a String) -> Self { - Response::Ok() - .content_type("text/plain; charset=utf-8") - .body(val) - } -} - -impl From for Response { - fn from(val: Bytes) -> Self { - Response::Ok() - .content_type("application/octet-stream") - .body(val) - } -} - -impl From for Response { - fn from(val: BytesMut) -> Self { - Response::Ok() - .content_type("application/octet-stream") - .body(val) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::body::Body; - use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE}; - - #[test] - fn test_debug() { - let resp = Response::Ok() - .header(COOKIE, HeaderValue::from_static("cookie1=value1; ")) - .header(COOKIE, HeaderValue::from_static("cookie2=value2; ")) - .finish(); - let dbg = format!("{:?}", resp); - assert!(dbg.contains("Response")); - } - - #[test] - fn test_response_cookies() { - use crate::httpmessage::HttpMessage; - - let req = crate::test::TestRequest::default() - .header(COOKIE, "cookie1=value1") - .header(COOKIE, "cookie2=value2") - .finish(); - let cookies = req.cookies().unwrap(); - - let resp = Response::Ok() - .cookie( - crate::http::Cookie::build("name", "value") - .domain("www.rust-lang.org") - .path("/test") - .http_only(true) - .max_age_time(time::Duration::days(1)) - .finish(), - ) - .del_cookie(&cookies[1]) - .finish(); - - let mut val: Vec<_> = resp - .headers() - .get_all(SET_COOKIE) - .map(|v| v.to_str().unwrap().to_owned()) - .collect(); - val.sort(); - assert!(val[0].starts_with("cookie1=; Max-Age=0;")); - assert_eq!( - val[1], - "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" - ); - } - - #[test] - fn test_update_response_cookies() { - let mut r = Response::Ok() - .cookie(crate::http::Cookie::new("original", "val100")) - .finish(); - - r.add_cookie(&crate::http::Cookie::new("cookie2", "val200")) - .unwrap(); - r.add_cookie(&crate::http::Cookie::new("cookie2", "val250")) - .unwrap(); - r.add_cookie(&crate::http::Cookie::new("cookie3", "val300")) - .unwrap(); - - assert_eq!(r.cookies().count(), 4); - r.del_cookie("cookie2"); - - let mut iter = r.cookies(); - let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("cookie3", "val300")); - let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("original", "val100")); - } - - #[test] - fn test_basic_builder() { - let resp = Response::Ok().header("X-TEST", "value").finish(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[test] - fn test_upgrade() { - let resp = Response::build(StatusCode::OK) - .upgrade("websocket") - .finish(); - assert!(resp.upgrade()); - assert_eq!( - resp.headers().get(header::UPGRADE).unwrap(), - HeaderValue::from_static("websocket") - ); - } - - #[test] - fn test_force_close() { - let resp = Response::build(StatusCode::OK).force_close().finish(); - assert!(!resp.keep_alive()) - } - - #[test] - fn test_content_type() { - let resp = Response::build(StatusCode::OK) - .content_type("text/plain") - .body(Body::Empty); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") - } - - #[test] - fn test_json() { - let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - } - - #[test] - fn test_json_ct() { - let resp = Response::build(StatusCode::OK) - .header(CONTENT_TYPE, "text/json") - .json(vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - } - - #[test] - fn test_json2() { - let resp = Response::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - } - - #[test] - fn test_json2_ct() { - let resp = Response::build(StatusCode::OK) - .header(CONTENT_TYPE, "text/json") - .json2(&vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - } - - #[test] - fn test_serde_json_in_body() { - use serde_json::json; - let resp = - Response::build(StatusCode::OK).body(json!({"test-key":"test-value"})); - assert_eq!(resp.body().get_ref(), br#"{"test-key":"test-value"}"#); - } - - #[test] - fn test_into_response() { - let resp: Response = "test".into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - - let resp: Response = b"test".as_ref().into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - - let resp: Response = "test".to_owned().into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - - let resp: Response = (&"test".to_owned()).into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - - let b = Bytes::from_static(b"test"); - let resp: Response = b.into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - - let b = Bytes::from_static(b"test"); - let resp: Response = b.into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - - let b = BytesMut::from("test"); - let resp: Response = b.into(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().get_ref(), b"test"); - } - - #[test] - fn test_into_builder() { - let mut resp: Response = "test".into(); - assert_eq!(resp.status(), StatusCode::OK); - - resp.add_cookie(&crate::http::Cookie::new("cookie1", "val100")) - .unwrap(); - - let mut builder: ResponseBuilder = resp.into(); - let resp = builder.status(StatusCode::BAD_REQUEST).finish(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let cookie = resp.cookies().next().unwrap(); - assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100")); - } -} diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs deleted file mode 100644 index 51de95135..000000000 --- a/actix-http/src/service.rs +++ /dev/null @@ -1,683 +0,0 @@ -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, net, rc}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_rt::net::TcpStream; -use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; -use bytes::Bytes; -use futures_core::{ready, Future}; -use futures_util::future::ok; -use h2::server::{self, Handshake}; -use pin_project::{pin_project, project}; - -use crate::body::MessageBody; -use crate::builder::HttpServiceBuilder; -use crate::cloneable::CloneableService; -use crate::config::{KeepAlive, ServiceConfig}; -use crate::error::{DispatchError, Error}; -use crate::helpers::DataFactory; -use crate::request::Request; -use crate::response::Response; -use crate::{h1, h2::Dispatcher, Protocol}; - -/// `ServiceFactory` HTTP1.1/HTTP2 transport implementation -pub struct HttpService> { - srv: S, - cfg: ServiceConfig, - expect: X, - upgrade: Option, - on_connect: Option Box>>, - _t: PhantomData<(T, B)>, -} - -impl HttpService -where - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, -{ - /// Create builder for `HttpService` instance. - pub fn build() -> HttpServiceBuilder { - HttpServiceBuilder::new() - } -} - -impl HttpService -where - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, -{ - /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { - let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0, false, None); - - HttpService { - cfg, - srv: service.into_factory(), - expect: h1::ExpectHandler, - upgrade: None, - on_connect: None, - _t: PhantomData, - } - } - - /// Create new `HttpService` instance with config. - pub(crate) fn with_config>( - cfg: ServiceConfig, - service: F, - ) -> Self { - HttpService { - cfg, - srv: service.into_factory(), - expect: h1::ExpectHandler, - upgrade: None, - on_connect: None, - _t: PhantomData, - } - } -} - -impl HttpService -where - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody, -{ - /// Provide service for `EXPECT: 100-Continue` support. - /// - /// Service get called with request that contains `EXPECT` header. - /// Service must return request in case of success, in that case - /// request will be forwarded to main service. - pub fn expect(self, expect: X1) -> HttpService - where - X1: ServiceFactory, - X1::Error: Into, - X1::InitError: fmt::Debug, - ::Future: 'static, - { - HttpService { - expect, - cfg: self.cfg, - srv: self.srv, - upgrade: self.upgrade, - on_connect: self.on_connect, - _t: PhantomData, - } - } - - /// Provide service for custom `Connection: UPGRADE` support. - /// - /// If service is provided then normal requests handling get halted - /// and this service get called with original request and framed object. - pub fn upgrade(self, upgrade: Option) -> HttpService - where - U1: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, - U1::Error: fmt::Display, - U1::InitError: fmt::Debug, - ::Future: 'static, - { - HttpService { - upgrade, - cfg: self.cfg, - srv: self.srv, - expect: self.expect, - on_connect: self.on_connect, - _t: PhantomData, - } - } - - /// Set on connect callback. - pub(crate) fn on_connect( - mut self, - f: Option Box>>, - ) -> Self { - self.on_connect = f; - self - } -} - -impl HttpService -where - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - ::Future: 'static, - U: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, - U::Error: fmt::Display + Into, - U::InitError: fmt::Debug, - ::Future: 'static, -{ - /// Create simple tcp stream service - pub fn tcp( - self, - ) -> impl ServiceFactory< - Config = (), - Request = TcpStream, - Response = (), - Error = DispatchError, - InitError = (), - > { - pipeline_factory(|io: TcpStream| { - let peer_addr = io.peer_addr().ok(); - ok((io, Protocol::Http1, peer_addr)) - }) - .and_then(self) - } -} - -#[cfg(feature = "openssl")] -mod openssl { - use super::*; - use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream}; - use actix_tls::{openssl::HandshakeError, SslError}; - - impl HttpService, S, B, X, U> - where - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - ::Future: 'static, - U: ServiceFactory< - Config = (), - Request = (Request, Framed, h1::Codec>), - Response = (), - >, - U::Error: fmt::Display + Into, - U::InitError: fmt::Debug, - ::Future: 'static, - { - /// Create openssl based service - pub fn openssl( - self, - acceptor: SslAcceptor, - ) -> impl ServiceFactory< - Config = (), - Request = TcpStream, - Response = (), - Error = SslError, DispatchError>, - InitError = (), - > { - pipeline_factory( - Acceptor::new(acceptor) - .map_err(SslError::Ssl) - .map_init_err(|_| panic!()), - ) - .and_then(|io: SslStream| { - let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() { - if protos.windows(2).any(|window| window == b"h2") { - Protocol::Http2 - } else { - Protocol::Http1 - } - } else { - Protocol::Http1 - }; - let peer_addr = io.get_ref().peer_addr().ok(); - ok((io, proto, peer_addr)) - }) - .and_then(self.map_err(SslError::Service)) - } - } -} - -#[cfg(feature = "rustls")] -mod rustls { - use super::*; - use actix_tls::rustls::{Acceptor, ServerConfig, Session, TlsStream}; - use actix_tls::SslError; - use std::io; - - impl HttpService, S, B, X, U> - where - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - ::Future: 'static, - U: ServiceFactory< - Config = (), - Request = (Request, Framed, h1::Codec>), - Response = (), - >, - U::Error: fmt::Display + Into, - U::InitError: fmt::Debug, - ::Future: 'static, - { - /// Create openssl based service - pub fn rustls( - self, - mut config: ServerConfig, - ) -> impl ServiceFactory< - Config = (), - Request = TcpStream, - Response = (), - Error = SslError, - InitError = (), - > { - let protos = vec!["h2".to_string().into(), "http/1.1".to_string().into()]; - config.set_protocols(&protos); - - pipeline_factory( - Acceptor::new(config) - .map_err(SslError::Ssl) - .map_init_err(|_| panic!()), - ) - .and_then(|io: TlsStream| { - let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol() { - if protos.windows(2).any(|window| window == b"h2") { - Protocol::Http2 - } else { - Protocol::Http1 - } - } else { - Protocol::Http1 - }; - let peer_addr = io.get_ref().0.peer_addr().ok(); - ok((io, proto, peer_addr)) - }) - .and_then(self.map_err(SslError::Service)) - } - } -} - -impl ServiceFactory for HttpService -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - ::Future: 'static, - U: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, - U::Error: fmt::Display + Into, - U::InitError: fmt::Debug, - ::Future: 'static, -{ - type Config = (); - type Request = (T, Protocol, Option); - type Response = (); - type Error = DispatchError; - type InitError = (); - type Service = HttpServiceHandler; - type Future = HttpServiceResponse; - - fn new_service(&self, _: ()) -> Self::Future { - HttpServiceResponse { - fut: self.srv.new_service(()), - fut_ex: Some(self.expect.new_service(())), - fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), - expect: None, - upgrade: None, - on_connect: self.on_connect.clone(), - cfg: self.cfg.clone(), - _t: PhantomData, - } - } -} - -#[doc(hidden)] -#[pin_project] -pub struct HttpServiceResponse< - T, - S: ServiceFactory, - B, - X: ServiceFactory, - U: ServiceFactory, -> { - #[pin] - fut: S::Future, - #[pin] - fut_ex: Option, - #[pin] - fut_upg: Option, - expect: Option, - upgrade: Option, - on_connect: Option Box>>, - cfg: ServiceConfig, - _t: PhantomData<(T, B)>, -} - -impl Future for HttpServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, - X: ServiceFactory, - X::Error: Into, - X::InitError: fmt::Debug, - ::Future: 'static, - U: ServiceFactory), Response = ()>, - U::Error: fmt::Display, - U::InitError: fmt::Debug, - ::Future: 'static, -{ - type Output = - Result, ()>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.as_mut().project(); - - if let Some(fut) = this.fut_ex.as_pin_mut() { - let expect = ready!(fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this = self.as_mut().project(); - *this.expect = Some(expect); - this.fut_ex.set(None); - } - - if let Some(fut) = this.fut_upg.as_pin_mut() { - let upgrade = ready!(fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e)))?; - this = self.as_mut().project(); - *this.upgrade = Some(upgrade); - this.fut_ex.set(None); - } - - let result = ready!(this - .fut - .poll(cx) - .map_err(|e| log::error!("Init http service error: {:?}", e))); - Poll::Ready(result.map(|service| { - let this = self.as_mut().project(); - HttpServiceHandler::new( - this.cfg.clone(), - service, - this.expect.take().unwrap(), - this.upgrade.take(), - this.on_connect.clone(), - ) - })) - } -} - -/// `Service` implementation for http transport -pub struct HttpServiceHandler { - srv: CloneableService, - expect: CloneableService, - upgrade: Option>, - cfg: ServiceConfig, - on_connect: Option Box>>, - _t: PhantomData<(T, B, X)>, -} - -impl HttpServiceHandler -where - S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ - fn new( - cfg: ServiceConfig, - srv: S, - expect: X, - upgrade: Option, - on_connect: Option Box>>, - ) -> HttpServiceHandler { - HttpServiceHandler { - cfg, - on_connect, - srv: CloneableService::new(srv), - expect: CloneableService::new(expect), - upgrade: upgrade.map(CloneableService::new), - _t: PhantomData, - } - } -} - -impl Service for HttpServiceHandler -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display + Into, -{ - type Request = (T, Protocol, Option); - type Response = (); - type Error = DispatchError; - type Future = HttpServiceHandlerResponse; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let ready = self - .expect - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready(); - - let ready = self - .srv - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready() - && ready; - - let ready = if let Some(ref mut upg) = self.upgrade { - upg.poll_ready(cx) - .map_err(|e| { - let e = e.into(); - log::error!("Http service readiness error: {:?}", e); - DispatchError::Service(e) - })? - .is_ready() - && ready - } else { - ready - }; - - if ready { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - } - - fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future { - let on_connect = if let Some(ref on_connect) = self.on_connect { - Some(on_connect(&io)) - } else { - None - }; - - match proto { - Protocol::Http2 => HttpServiceHandlerResponse { - state: State::H2Handshake(Some(( - server::handshake(io), - self.cfg.clone(), - self.srv.clone(), - on_connect, - peer_addr, - ))), - }, - Protocol::Http1 => HttpServiceHandlerResponse { - state: State::H1(h1::Dispatcher::new( - io, - self.cfg.clone(), - self.srv.clone(), - self.expect.clone(), - self.upgrade.clone(), - on_connect, - peer_addr, - )), - }, - } - } -} - -#[pin_project] -enum State -where - S: Service, - S::Future: 'static, - S::Error: Into, - T: AsyncRead + AsyncWrite + Unpin, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ - H1(#[pin] h1::Dispatcher), - H2(#[pin] Dispatcher), - H2Handshake( - Option<( - Handshake, - ServiceConfig, - CloneableService, - Option>, - Option, - )>, - ), -} - -#[pin_project] -pub struct HttpServiceHandlerResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ - #[pin] - state: State, -} - -impl Future for HttpServiceHandlerResponse -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into + 'static, - S::Future: 'static, - S::Response: Into> + 'static, - B: MessageBody, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ - type Output = Result<(), DispatchError>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.project().state.poll(cx) - } -} - -impl State -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into + 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, - X: Service, - X::Error: Into, - U: Service), Response = ()>, - U::Error: fmt::Display, -{ - #[project] - fn poll( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - #[project] - match self.as_mut().project() { - State::H1(disp) => disp.poll(cx), - State::H2(disp) => disp.poll(cx), - State::H2Handshake(ref mut data) => { - let conn = if let Some(ref mut item) = data { - match Pin::new(&mut item.0).poll(cx) { - Poll::Ready(Ok(conn)) => conn, - Poll::Ready(Err(err)) => { - trace!("H2 handshake error: {}", err); - return Poll::Ready(Err(err.into())); - } - Poll::Pending => return Poll::Pending, - } - } else { - panic!() - }; - let (_, cfg, srv, on_connect, peer_addr) = data.take().unwrap(); - self.set(State::H2(Dispatcher::new( - srv, conn, on_connect, cfg, None, peer_addr, - ))); - self.poll(cx) - } - } - } -} diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs deleted file mode 100644 index 061ba610f..000000000 --- a/actix-http/src/test.rs +++ /dev/null @@ -1,272 +0,0 @@ -//! Test Various helpers for Actix applications to use during testing. -use std::convert::TryFrom; -use std::fmt::Write as FmtWrite; -use std::io::{self, Read, Write}; -use std::pin::Pin; -use std::str::FromStr; -use std::task::{Context, Poll}; - -use actix_codec::{AsyncRead, AsyncWrite}; -use bytes::{Bytes, BytesMut}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{Error as HttpError, Method, Uri, Version}; -use percent_encoding::percent_encode; - -use crate::cookie::{Cookie, CookieJar, USERINFO}; -use crate::header::HeaderMap; -use crate::header::{Header, IntoHeaderValue}; -use crate::payload::Payload; -use crate::Request; - -/// Test `Request` builder -/// -/// ```rust,ignore -/// # use http::{header, StatusCode}; -/// # use actix_web::*; -/// use actix_web::test::TestRequest; -/// -/// fn index(req: &HttpRequest) -> Response { -/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { -/// Response::Ok().into() -/// } else { -/// Response::BadRequest().into() -/// } -/// } -/// -/// let resp = TestRequest::with_header("content-type", "text/plain") -/// .run(&index) -/// .unwrap(); -/// assert_eq!(resp.status(), StatusCode::OK); -/// -/// let resp = TestRequest::default().run(&index).unwrap(); -/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); -/// ``` -pub struct TestRequest(Option); - -struct Inner { - version: Version, - method: Method, - uri: Uri, - headers: HeaderMap, - cookies: CookieJar, - payload: Option, -} - -impl Default for TestRequest { - fn default() -> TestRequest { - TestRequest(Some(Inner { - method: Method::GET, - uri: Uri::from_str("/").unwrap(), - version: Version::HTTP_11, - headers: HeaderMap::new(), - cookies: CookieJar::new(), - payload: None, - })) - } -} - -impl TestRequest { - /// Create TestRequest and set request uri - pub fn with_uri(path: &str) -> TestRequest { - TestRequest::default().uri(path).take() - } - - /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestRequest { - TestRequest::default().set(hdr).take() - } - - /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> TestRequest - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - TestRequest::default().header(key, value).take() - } - - /// Set HTTP version of this request - pub fn version(&mut self, ver: Version) -> &mut Self { - parts(&mut self.0).version = ver; - self - } - - /// Set HTTP method of this request - pub fn method(&mut self, meth: Method) -> &mut Self { - parts(&mut self.0).method = meth; - self - } - - /// Set HTTP Uri of this request - pub fn uri(&mut self, path: &str) -> &mut Self { - parts(&mut self.0).uri = Uri::from_str(path).unwrap(); - self - } - - /// Set a header - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Ok(value) = hdr.try_into() { - parts(&mut self.0).headers.append(H::name(), value); - return self; - } - panic!("Can not set header"); - } - - /// Set a header - pub fn header(&mut self, key: K, value: V) -> &mut Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - if let Ok(key) = HeaderName::try_from(key) { - if let Ok(value) = value.try_into() { - parts(&mut self.0).headers.append(key, value); - return self; - } - } - panic!("Can not create header"); - } - - /// Set cookie for this request - pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { - parts(&mut self.0).cookies.add(cookie.into_owned()); - self - } - - /// Set request payload - pub fn set_payload>(&mut self, data: B) -> &mut Self { - let mut payload = crate::h1::Payload::empty(); - payload.unread_data(data.into()); - parts(&mut self.0).payload = Some(payload.into()); - self - } - - pub fn take(&mut self) -> TestRequest { - TestRequest(self.0.take()) - } - - /// Complete request creation and generate `Request` instance - pub fn finish(&mut self) -> Request { - let inner = self.0.take().expect("cannot reuse test request builder"); - - let mut req = if let Some(pl) = inner.payload { - Request::with_payload(pl) - } else { - Request::with_payload(crate::h1::Payload::empty().into()) - }; - - let head = req.head_mut(); - head.uri = inner.uri; - head.method = inner.method; - head.version = inner.version; - head.headers = inner.headers; - - let mut cookie = String::new(); - for c in inner.cookies.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO); - let value = percent_encode(c.value().as_bytes(), USERINFO); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - if !cookie.is_empty() { - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); - } - - req - } -} - -#[inline] -fn parts(parts: &mut Option) -> &mut Inner { - parts.as_mut().expect("cannot reuse test request builder") -} - -/// Async io buffer -pub struct TestBuffer { - pub read_buf: BytesMut, - pub write_buf: BytesMut, - pub err: Option, -} - -impl TestBuffer { - /// Create new TestBuffer instance - pub fn new(data: T) -> TestBuffer - where - BytesMut: From, - { - TestBuffer { - read_buf: BytesMut::from(data), - write_buf: BytesMut::new(), - err: None, - } - } - - /// Create new empty TestBuffer instance - pub fn empty() -> TestBuffer { - TestBuffer::new("") - } - - /// Add extra data to read buffer. - pub fn extend_read_buf>(&mut self, data: T) { - self.read_buf.extend_from_slice(data.as_ref()) - } -} - -impl io::Read for TestBuffer { - fn read(&mut self, dst: &mut [u8]) -> Result { - if self.read_buf.is_empty() { - if self.err.is_some() { - Err(self.err.take().unwrap()) - } else { - Err(io::Error::new(io::ErrorKind::WouldBlock, "")) - } - } else { - let size = std::cmp::min(self.read_buf.len(), dst.len()); - let b = self.read_buf.split_to(size); - dst[..size].copy_from_slice(&b); - Ok(size) - } - } -} - -impl io::Write for TestBuffer { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.write_buf.extend(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl AsyncRead for TestBuffer { - fn poll_read( - self: Pin<&mut Self>, - _: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - Poll::Ready(self.get_mut().read(buf)) - } -} - -impl AsyncWrite for TestBuffer { - fn poll_write( - self: Pin<&mut Self>, - _: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Poll::Ready(self.get_mut().write(buf)) - } - - fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } -} diff --git a/actix-http/src/time_parser.rs b/actix-http/src/time_parser.rs deleted file mode 100644 index f6623d24e..000000000 --- a/actix-http/src/time_parser.rs +++ /dev/null @@ -1,42 +0,0 @@ -use time::{PrimitiveDateTime, Date}; - -/// Attempt to parse a `time` string as one of either RFC 1123, RFC 850, or asctime. -pub fn parse_http_date(time: &str) -> Option { - try_parse_rfc_1123(time) - .or_else(|| try_parse_rfc_850(time)) - .or_else(|| try_parse_asctime(time)) -} - -/// Attempt to parse a `time` string as a RFC 1123 formatted date time string. -fn try_parse_rfc_1123(time: &str) -> Option { - time::parse(time, "%a, %d %b %Y %H:%M:%S").ok() -} - -/// Attempt to parse a `time` string as a RFC 850 formatted date time string. -fn try_parse_rfc_850(time: &str) -> Option { - match PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S") { - Ok(dt) => { - // If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3, - // we consider the year as part of this century if it's within the next 50 years, - // otherwise we consider as part of the previous century. - let now = PrimitiveDateTime::now(); - let century_start_year = (now.year() / 100) * 100; - let mut expanded_year = century_start_year + dt.year(); - - if expanded_year > now.year() + 50 { - expanded_year -= 100; - } - - match Date::try_from_ymd(expanded_year, dt.month(), dt.day()) { - Ok(date) => Some(PrimitiveDateTime::new(date, dt.time())), - Err(_) => None - } - } - Err(_) => None - } -} - -/// Attempt to parse a `time` string using ANSI C's `asctime` format. -fn try_parse_asctime(time: &str) -> Option { - time::parse(time, "%a %b %_d %H:%M:%S %Y").ok() -} diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs deleted file mode 100644 index a37208a2b..000000000 --- a/actix-http/src/ws/codec.rs +++ /dev/null @@ -1,284 +0,0 @@ -use actix_codec::{Decoder, Encoder}; -use bytes::{Bytes, BytesMut}; - -use super::frame::Parser; -use super::proto::{CloseReason, OpCode}; -use super::ProtocolError; - -/// `WebSocket` Message -#[derive(Debug, PartialEq)] -pub enum Message { - /// Text message - Text(String), - /// Binary message - Binary(Bytes), - /// Continuation - Continuation(Item), - /// Ping message - Ping(Bytes), - /// Pong message - Pong(Bytes), - /// Close message with optional reason - Close(Option), - /// No-op. Useful for actix-net services - Nop, -} - -/// `WebSocket` frame -#[derive(Debug, PartialEq)] -pub enum Frame { - /// Text frame, codec does not verify utf8 encoding - Text(Bytes), - /// Binary frame - Binary(Bytes), - /// Continuation - Continuation(Item), - /// Ping message - Ping(Bytes), - /// Pong message - Pong(Bytes), - /// Close message with optional reason - Close(Option), -} - -/// `WebSocket` continuation item -#[derive(Debug, PartialEq)] -pub enum Item { - FirstText(Bytes), - FirstBinary(Bytes), - Continue(Bytes), - Last(Bytes), -} - -#[derive(Debug, Copy, Clone)] -/// WebSockets protocol codec -pub struct Codec { - flags: Flags, - max_size: usize, -} - -bitflags::bitflags! { - struct Flags: u8 { - const SERVER = 0b0000_0001; - const CONTINUATION = 0b0000_0010; - const W_CONTINUATION = 0b0000_0100; - } -} - -impl Codec { - /// Create new websocket frames decoder - pub fn new() -> Codec { - Codec { - max_size: 65_536, - flags: Flags::SERVER, - } - } - - /// Set max frame size - /// - /// By default max size is set to 64kb - pub fn max_size(mut self, size: usize) -> Self { - self.max_size = size; - self - } - - /// Set decoder to client mode. - /// - /// By default decoder works in server mode. - pub fn client_mode(mut self) -> Self { - self.flags.remove(Flags::SERVER); - self - } -} - -impl Encoder for Codec { - type Item = Message; - type Error = ProtocolError; - - fn encode(&mut self, item: Message, dst: &mut BytesMut) -> Result<(), Self::Error> { - match item { - Message::Text(txt) => Parser::write_message( - dst, - txt, - OpCode::Text, - true, - !self.flags.contains(Flags::SERVER), - ), - Message::Binary(bin) => Parser::write_message( - dst, - bin, - OpCode::Binary, - true, - !self.flags.contains(Flags::SERVER), - ), - Message::Ping(txt) => Parser::write_message( - dst, - txt, - OpCode::Ping, - true, - !self.flags.contains(Flags::SERVER), - ), - Message::Pong(txt) => Parser::write_message( - dst, - txt, - OpCode::Pong, - true, - !self.flags.contains(Flags::SERVER), - ), - Message::Close(reason) => { - Parser::write_close(dst, reason, !self.flags.contains(Flags::SERVER)) - } - Message::Continuation(cont) => match cont { - Item::FirstText(data) => { - if self.flags.contains(Flags::W_CONTINUATION) { - return Err(ProtocolError::ContinuationStarted); - } else { - self.flags.insert(Flags::W_CONTINUATION); - Parser::write_message( - dst, - &data[..], - OpCode::Binary, - false, - !self.flags.contains(Flags::SERVER), - ) - } - } - Item::FirstBinary(data) => { - if self.flags.contains(Flags::W_CONTINUATION) { - return Err(ProtocolError::ContinuationStarted); - } else { - self.flags.insert(Flags::W_CONTINUATION); - Parser::write_message( - dst, - &data[..], - OpCode::Text, - false, - !self.flags.contains(Flags::SERVER), - ) - } - } - Item::Continue(data) => { - if self.flags.contains(Flags::W_CONTINUATION) { - Parser::write_message( - dst, - &data[..], - OpCode::Continue, - false, - !self.flags.contains(Flags::SERVER), - ) - } else { - return Err(ProtocolError::ContinuationNotStarted); - } - } - Item::Last(data) => { - if self.flags.contains(Flags::W_CONTINUATION) { - self.flags.remove(Flags::W_CONTINUATION); - Parser::write_message( - dst, - &data[..], - OpCode::Continue, - true, - !self.flags.contains(Flags::SERVER), - ) - } else { - return Err(ProtocolError::ContinuationNotStarted); - } - } - }, - Message::Nop => (), - } - Ok(()) - } -} - -impl Decoder for Codec { - type Item = Frame; - type Error = ProtocolError; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - match Parser::parse(src, self.flags.contains(Flags::SERVER), self.max_size) { - Ok(Some((finished, opcode, payload))) => { - // continuation is not supported - if !finished { - return match opcode { - OpCode::Continue => { - if self.flags.contains(Flags::CONTINUATION) { - Ok(Some(Frame::Continuation(Item::Continue( - payload - .map(|pl| pl.freeze()) - .unwrap_or_else(Bytes::new), - )))) - } else { - Err(ProtocolError::ContinuationNotStarted) - } - } - OpCode::Binary => { - if !self.flags.contains(Flags::CONTINUATION) { - self.flags.insert(Flags::CONTINUATION); - Ok(Some(Frame::Continuation(Item::FirstBinary( - payload - .map(|pl| pl.freeze()) - .unwrap_or_else(Bytes::new), - )))) - } else { - Err(ProtocolError::ContinuationStarted) - } - } - OpCode::Text => { - if !self.flags.contains(Flags::CONTINUATION) { - self.flags.insert(Flags::CONTINUATION); - Ok(Some(Frame::Continuation(Item::FirstText( - payload - .map(|pl| pl.freeze()) - .unwrap_or_else(Bytes::new), - )))) - } else { - Err(ProtocolError::ContinuationStarted) - } - } - _ => { - error!("Unfinished fragment {:?}", opcode); - Err(ProtocolError::ContinuationFragment(opcode)) - } - }; - } - - match opcode { - OpCode::Continue => { - if self.flags.contains(Flags::CONTINUATION) { - self.flags.remove(Flags::CONTINUATION); - Ok(Some(Frame::Continuation(Item::Last( - payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new), - )))) - } else { - Err(ProtocolError::ContinuationNotStarted) - } - } - OpCode::Bad => Err(ProtocolError::BadOpCode), - OpCode::Close => { - if let Some(ref pl) = payload { - let close_reason = Parser::parse_close_payload(pl); - Ok(Some(Frame::Close(close_reason))) - } else { - Ok(Some(Frame::Close(None))) - } - } - OpCode::Ping => Ok(Some(Frame::Ping( - payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new), - ))), - OpCode::Pong => Ok(Some(Frame::Pong( - payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new), - ))), - OpCode::Binary => Ok(Some(Frame::Binary( - payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new), - ))), - OpCode::Text => Ok(Some(Frame::Text( - payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new), - ))), - } - } - Ok(None) => Ok(None), - Err(e) => Err(e), - } - } -} diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs deleted file mode 100644 index 7a6b11b18..000000000 --- a/actix-http/src/ws/dispatcher.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_service::{IntoService, Service}; -use actix_utils::framed; - -use super::{Codec, Frame, Message}; - -pub struct Dispatcher -where - S: Service + 'static, - T: AsyncRead + AsyncWrite, -{ - inner: framed::Dispatcher, -} - -impl Dispatcher -where - T: AsyncRead + AsyncWrite, - S: Service, - S::Future: 'static, - S::Error: 'static, -{ - pub fn new>(io: T, service: F) -> Self { - Dispatcher { - inner: framed::Dispatcher::new(Framed::new(io, Codec::new()), service), - } - } - - pub fn with>(framed: Framed, service: F) -> Self { - Dispatcher { - inner: framed::Dispatcher::new(framed, service), - } - } -} - -impl Future for Dispatcher -where - T: AsyncRead + AsyncWrite, - S: Service, - S::Future: 'static, - S::Error: 'static, -{ - type Output = Result<(), framed::DispatcherError>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Pin::new(&mut self.inner).poll(cx) - } -} diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs deleted file mode 100644 index 3c70eb2bd..000000000 --- a/actix-http/src/ws/frame.rs +++ /dev/null @@ -1,384 +0,0 @@ -use std::convert::TryFrom; - -use bytes::{Buf, BufMut, BytesMut}; -use log::debug; -use rand; - -use crate::ws::mask::apply_mask; -use crate::ws::proto::{CloseCode, CloseReason, OpCode}; -use crate::ws::ProtocolError; - -/// A struct representing a `WebSocket` frame. -#[derive(Debug)] -pub struct Parser; - -impl Parser { - fn parse_metadata( - src: &[u8], - server: bool, - max_size: usize, - ) -> Result)>, ProtocolError> { - let chunk_len = src.len(); - - let mut idx = 2; - if chunk_len < 2 { - return Ok(None); - } - - let first = src[0]; - let second = src[1]; - let finished = first & 0x80 != 0; - - // check masking - let masked = second & 0x80 != 0; - if !masked && server { - return Err(ProtocolError::UnmaskedFrame); - } else if masked && !server { - return Err(ProtocolError::MaskedFrame); - } - - // Op code - let opcode = OpCode::from(first & 0x0F); - - if let OpCode::Bad = opcode { - return Err(ProtocolError::InvalidOpcode(first & 0x0F)); - } - - let len = second & 0x7F; - let length = if len == 126 { - if chunk_len < 4 { - return Ok(None); - } - let len = usize::from(u16::from_be_bytes( - TryFrom::try_from(&src[idx..idx + 2]).unwrap(), - )); - idx += 2; - len - } else if len == 127 { - if chunk_len < 10 { - return Ok(None); - } - let len = u64::from_be_bytes(TryFrom::try_from(&src[idx..idx + 8]).unwrap()); - if len > max_size as u64 { - return Err(ProtocolError::Overflow); - } - idx += 8; - len as usize - } else { - len as usize - }; - - // check for max allowed size - if length > max_size { - return Err(ProtocolError::Overflow); - } - - let mask = if server { - if chunk_len < idx + 4 { - return Ok(None); - } - - let mask = - u32::from_le_bytes(TryFrom::try_from(&src[idx..idx + 4]).unwrap()); - idx += 4; - Some(mask) - } else { - None - }; - - Ok(Some((idx, finished, opcode, length, mask))) - } - - /// Parse the input stream into a frame. - pub fn parse( - src: &mut BytesMut, - server: bool, - max_size: usize, - ) -> Result)>, ProtocolError> { - // try to parse ws frame metadata - let (idx, finished, opcode, length, mask) = - match Parser::parse_metadata(src, server, max_size)? { - None => return Ok(None), - Some(res) => res, - }; - - // not enough data - if src.len() < idx + length { - return Ok(None); - } - - // remove prefix - src.advance(idx); - - // no need for body - if length == 0 { - return Ok(Some((finished, opcode, None))); - } - - let mut data = src.split_to(length); - - // control frames must have length <= 125 - match opcode { - OpCode::Ping | OpCode::Pong if length > 125 => { - return Err(ProtocolError::InvalidLength(length)); - } - OpCode::Close if length > 125 => { - debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); - return Ok(Some((true, OpCode::Close, None))); - } - _ => (), - } - - // unmask - if let Some(mask) = mask { - apply_mask(&mut data, mask); - } - - Ok(Some((finished, opcode, Some(data)))) - } - - /// Parse the payload of a close frame. - pub fn parse_close_payload(payload: &[u8]) -> Option { - if payload.len() >= 2 { - let raw_code = u16::from_be_bytes(TryFrom::try_from(&payload[..2]).unwrap()); - let code = CloseCode::from(raw_code); - let description = if payload.len() > 2 { - Some(String::from_utf8_lossy(&payload[2..]).into()) - } else { - None - }; - Some(CloseReason { code, description }) - } else { - None - } - } - - /// Generate binary representation - pub fn write_message>( - dst: &mut BytesMut, - pl: B, - op: OpCode, - fin: bool, - mask: bool, - ) { - let payload = pl.as_ref(); - let one: u8 = if fin { - 0x80 | Into::::into(op) - } else { - op.into() - }; - let payload_len = payload.len(); - let (two, p_len) = if mask { - (0x80, payload_len + 4) - } else { - (0, payload_len) - }; - - if payload_len < 126 { - dst.reserve(p_len + 2 + if mask { 4 } else { 0 }); - dst.put_slice(&[one, two | payload_len as u8]); - } else if payload_len <= 65_535 { - dst.reserve(p_len + 4 + if mask { 4 } else { 0 }); - dst.put_slice(&[one, two | 126]); - dst.put_u16(payload_len as u16); - } else { - dst.reserve(p_len + 10 + if mask { 4 } else { 0 }); - dst.put_slice(&[one, two | 127]); - dst.put_u64(payload_len as u64); - }; - - if mask { - let mask = rand::random::(); - dst.put_u32_le(mask); - dst.put_slice(payload.as_ref()); - let pos = dst.len() - payload_len; - apply_mask(&mut dst[pos..], mask); - } else { - dst.put_slice(payload.as_ref()); - } - } - - /// Create a new Close control frame. - #[inline] - pub fn write_close(dst: &mut BytesMut, reason: Option, mask: bool) { - let payload = match reason { - None => Vec::new(), - Some(reason) => { - let mut payload = Into::::into(reason.code).to_be_bytes().to_vec(); - if let Some(description) = reason.description { - payload.extend(description.as_bytes()); - } - payload - } - }; - - Parser::write_message(dst, payload, OpCode::Close, true, mask) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bytes::Bytes; - - struct F { - finished: bool, - opcode: OpCode, - payload: Bytes, - } - - fn is_none( - frm: &Result)>, ProtocolError>, - ) -> bool { - match *frm { - Ok(None) => true, - _ => false, - } - } - - fn extract( - frm: Result)>, ProtocolError>, - ) -> F { - match frm { - Ok(Some((finished, opcode, payload))) => F { - finished, - opcode, - payload: payload - .map(|b| b.freeze()) - .unwrap_or_else(|| Bytes::from("")), - }, - _ => unreachable!("error"), - } - } - - #[test] - fn test_parse() { - let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); - assert!(is_none(&Parser::parse(&mut buf, false, 1024))); - - let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); - buf.extend(b"1"); - - let frame = extract(Parser::parse(&mut buf, false, 1024)); - assert!(!frame.finished); - assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload.as_ref(), &b"1"[..]); - } - - #[test] - fn test_parse_length0() { - let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0000u8][..]); - let frame = extract(Parser::parse(&mut buf, false, 1024)); - assert!(!frame.finished); - assert_eq!(frame.opcode, OpCode::Text); - assert!(frame.payload.is_empty()); - } - - #[test] - fn test_parse_length2() { - let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); - assert!(is_none(&Parser::parse(&mut buf, false, 1024))); - - let mut buf = BytesMut::from(&[0b0000_0001u8, 126u8][..]); - buf.extend(&[0u8, 4u8][..]); - buf.extend(b"1234"); - - let frame = extract(Parser::parse(&mut buf, false, 1024)); - assert!(!frame.finished); - assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload.as_ref(), &b"1234"[..]); - } - - #[test] - fn test_parse_length4() { - let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); - assert!(is_none(&Parser::parse(&mut buf, false, 1024))); - - let mut buf = BytesMut::from(&[0b0000_0001u8, 127u8][..]); - buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]); - buf.extend(b"1234"); - - let frame = extract(Parser::parse(&mut buf, false, 1024)); - assert!(!frame.finished); - assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload.as_ref(), &b"1234"[..]); - } - - #[test] - fn test_parse_frame_mask() { - let mut buf = BytesMut::from(&[0b0000_0001u8, 0b1000_0001u8][..]); - buf.extend(b"0001"); - buf.extend(b"1"); - - assert!(Parser::parse(&mut buf, false, 1024).is_err()); - - let frame = extract(Parser::parse(&mut buf, true, 1024)); - assert!(!frame.finished); - assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload, Bytes::from(vec![1u8])); - } - - #[test] - fn test_parse_frame_no_mask() { - let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0001u8][..]); - buf.extend(&[1u8]); - - assert!(Parser::parse(&mut buf, true, 1024).is_err()); - - let frame = extract(Parser::parse(&mut buf, false, 1024)); - assert!(!frame.finished); - assert_eq!(frame.opcode, OpCode::Text); - assert_eq!(frame.payload, Bytes::from(vec![1u8])); - } - - #[test] - fn test_parse_frame_max_size() { - let mut buf = BytesMut::from(&[0b0000_0001u8, 0b0000_0010u8][..]); - buf.extend(&[1u8, 1u8]); - - assert!(Parser::parse(&mut buf, true, 1).is_err()); - - if let Err(ProtocolError::Overflow) = Parser::parse(&mut buf, false, 0) { - } else { - unreachable!("error"); - } - } - - #[test] - fn test_ping_frame() { - let mut buf = BytesMut::new(); - Parser::write_message(&mut buf, Vec::from("data"), OpCode::Ping, true, false); - - let mut v = vec![137u8, 4u8]; - v.extend(b"data"); - assert_eq!(&buf[..], &v[..]); - } - - #[test] - fn test_pong_frame() { - let mut buf = BytesMut::new(); - Parser::write_message(&mut buf, Vec::from("data"), OpCode::Pong, true, false); - - let mut v = vec![138u8, 4u8]; - v.extend(b"data"); - assert_eq!(&buf[..], &v[..]); - } - - #[test] - fn test_close_frame() { - let mut buf = BytesMut::new(); - let reason = (CloseCode::Normal, "data"); - Parser::write_close(&mut buf, Some(reason.into()), false); - - let mut v = vec![136u8, 6u8, 3u8, 232u8]; - v.extend(b"data"); - assert_eq!(&buf[..], &v[..]); - } - - #[test] - fn test_empty_close_frame() { - let mut buf = BytesMut::new(); - Parser::write_close(&mut buf, None, false); - assert_eq!(&buf[..], &vec![0x88, 0x00][..]); - } -} diff --git a/actix-http/src/ws/mask.rs b/actix-http/src/ws/mask.rs deleted file mode 100644 index 7eb5d148f..000000000 --- a/actix-http/src/ws/mask.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) -#![allow(clippy::cast_ptr_alignment)] -use std::ptr::copy_nonoverlapping; -use std::slice; - -// Holds a slice guaranteed to be shorter than 8 bytes -struct ShortSlice<'a>(&'a mut [u8]); - -impl<'a> ShortSlice<'a> { - unsafe fn new(slice: &'a mut [u8]) -> Self { - // Sanity check for debug builds - debug_assert!(slice.len() < 8); - ShortSlice(slice) - } - fn len(&self) -> usize { - self.0.len() - } -} - -/// Faster version of `apply_mask()` which operates on 8-byte blocks. -#[inline] -#[allow(clippy::cast_lossless)] -pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { - // Extend the mask to 64 bits - let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64); - // Split the buffer into three segments - let (head, mid, tail) = align_buf(buf); - - // Initial unaligned segment - let head_len = head.len(); - if head_len > 0 { - xor_short(head, mask_u64); - if cfg!(target_endian = "big") { - mask_u64 = mask_u64.rotate_left(8 * head_len as u32); - } else { - mask_u64 = mask_u64.rotate_right(8 * head_len as u32); - } - } - // Aligned segment - for v in mid { - *v ^= mask_u64; - } - // Final unaligned segment - if tail.len() > 0 { - xor_short(tail, mask_u64); - } -} - -#[inline] -// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so -// inefficient, it could be done better. The compiler does not understand that -// a `ShortSlice` must be smaller than a u64. -#[allow(clippy::needless_pass_by_value)] -fn xor_short(buf: ShortSlice<'_>, mask: u64) { - // Unsafe: we know that a `ShortSlice` fits in a u64 - unsafe { - let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len()); - let mut b: u64 = 0; - #[allow(trivial_casts)] - copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len); - b ^= mask; - #[allow(trivial_casts)] - copy_nonoverlapping(&b as *const _ as *const u8, ptr, len); - } -} - -#[inline] -// Unsafe: caller must ensure the buffer has the correct size and alignment -unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] { - // Assert correct size and alignment in debug builds - debug_assert!(buf.len().trailing_zeros() >= 3); - debug_assert!((buf.as_ptr() as usize).trailing_zeros() >= 3); - - slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3) -} - -#[inline] -// Splits a slice into three parts: an unaligned short head and tail, plus an aligned -// u64 mid section. -fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) { - let start_ptr = buf.as_ptr() as usize; - let end_ptr = start_ptr + buf.len(); - - // Round *up* to next aligned boundary for start - let start_aligned = (start_ptr + 7) & !0x7; - // Round *down* to last aligned boundary for end - let end_aligned = end_ptr & !0x7; - - if end_aligned >= start_aligned { - // We have our three segments (head, mid, tail) - let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr); - let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr); - - // Unsafe: we know the middle section is correctly aligned, and the outer - // sections are smaller than 8 bytes - unsafe { (ShortSlice::new(head), cast_slice(mid), ShortSlice(tail)) } - } else { - // We didn't cross even one aligned boundary! - - // Unsafe: The outer sections are smaller than 8 bytes - unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) } - } -} - -#[cfg(test)] -mod tests { - use super::apply_mask; - - /// A safe unoptimized mask application. - fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) { - for (i, byte) in buf.iter_mut().enumerate() { - *byte ^= mask[i & 3]; - } - } - - #[test] - fn test_apply_mask() { - let mask = [0x6d, 0xb6, 0xb2, 0x80]; - let mask_u32 = u32::from_le_bytes(mask); - - let unmasked = vec![ - 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, - 0x74, 0xf9, 0x12, 0x03, - ]; - - // Check masking with proper alignment. - { - let mut masked = unmasked.clone(); - apply_mask_fallback(&mut masked, &mask); - - let mut masked_fast = unmasked.clone(); - apply_mask(&mut masked_fast, mask_u32); - - assert_eq!(masked, masked_fast); - } - - // Check masking without alignment. - { - let mut masked = unmasked.clone(); - apply_mask_fallback(&mut masked[1..], &mask); - - let mut masked_fast = unmasked.clone(); - apply_mask(&mut masked_fast[1..], mask_u32); - - assert_eq!(masked, masked_fast); - } - } -} diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs deleted file mode 100644 index ffa397979..000000000 --- a/actix-http/src/ws/mod.rs +++ /dev/null @@ -1,318 +0,0 @@ -//! WebSocket protocol support. -//! -//! To setup a `WebSocket`, first do web socket handshake then on success -//! convert `Payload` into a `WsStream` stream and then use `WsWriter` to -//! communicate with the peer. -use std::io; - -use derive_more::{Display, From}; -use http::{header, Method, StatusCode}; - -use crate::error::ResponseError; -use crate::message::RequestHead; -use crate::response::{Response, ResponseBuilder}; - -mod codec; -mod dispatcher; -mod frame; -mod mask; -mod proto; - -pub use self::codec::{Codec, Frame, Item, Message}; -pub use self::dispatcher::Dispatcher; -pub use self::frame::Parser; -pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; - -/// Websocket protocol errors -#[derive(Debug, Display, From)] -pub enum ProtocolError { - /// Received an unmasked frame from client - #[display(fmt = "Received an unmasked frame from client")] - UnmaskedFrame, - /// Received a masked frame from server - #[display(fmt = "Received a masked frame from server")] - MaskedFrame, - /// Encountered invalid opcode - #[display(fmt = "Invalid opcode: {}", _0)] - InvalidOpcode(u8), - /// Invalid control frame length - #[display(fmt = "Invalid control frame length: {}", _0)] - InvalidLength(usize), - /// Bad web socket op code - #[display(fmt = "Bad web socket op code")] - BadOpCode, - /// A payload reached size limit. - #[display(fmt = "A payload reached size limit.")] - Overflow, - /// Continuation is not started - #[display(fmt = "Continuation is not started.")] - ContinuationNotStarted, - /// Received new continuation but it is already started - #[display(fmt = "Received new continuation but it is already started")] - ContinuationStarted, - /// Unknown continuation fragment - #[display(fmt = "Unknown continuation fragment.")] - ContinuationFragment(OpCode), - /// Io error - #[display(fmt = "io error: {}", _0)] - Io(io::Error), -} - -impl ResponseError for ProtocolError {} - -/// Websocket handshake errors -#[derive(PartialEq, Debug, Display)] -pub enum HandshakeError { - /// Only get method is allowed - #[display(fmt = "Method not allowed")] - GetMethodRequired, - /// Upgrade header if not set to websocket - #[display(fmt = "Websocket upgrade is expected")] - NoWebsocketUpgrade, - /// Connection header is not set to upgrade - #[display(fmt = "Connection upgrade is expected")] - NoConnectionUpgrade, - /// Websocket version header is not set - #[display(fmt = "Websocket version header is required")] - NoVersionHeader, - /// Unsupported websocket version - #[display(fmt = "Unsupported version")] - UnsupportedVersion, - /// Websocket key is not set or wrong - #[display(fmt = "Unknown websocket key")] - BadWebsocketKey, -} - -impl ResponseError for HandshakeError { - fn error_response(&self) -> Response { - match *self { - HandshakeError::GetMethodRequired => Response::MethodNotAllowed() - .header(header::ALLOW, "GET") - .finish(), - HandshakeError::NoWebsocketUpgrade => Response::BadRequest() - .reason("No WebSocket UPGRADE header found") - .finish(), - HandshakeError::NoConnectionUpgrade => Response::BadRequest() - .reason("No CONNECTION upgrade") - .finish(), - HandshakeError::NoVersionHeader => Response::BadRequest() - .reason("Websocket version header is required") - .finish(), - HandshakeError::UnsupportedVersion => Response::BadRequest() - .reason("Unsupported version") - .finish(), - HandshakeError::BadWebsocketKey => { - Response::BadRequest().reason("Handshake error").finish() - } - } - } -} - -/// Verify `WebSocket` handshake request and create handshake reponse. -// /// `protocols` is a sequence of known protocols. On successful handshake, -// /// the returned response headers contain the first protocol in this list -// /// which the server also knows. -pub fn handshake(req: &RequestHead) -> Result { - verify_handshake(req)?; - Ok(handshake_response(req)) -} - -/// Verify `WebSocket` handshake request. -// /// `protocols` is a sequence of known protocols. On successful handshake, -// /// the returned response headers contain the first protocol in this list -// /// which the server also knows. -pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> { - // WebSocket accepts only GET - if req.method != Method::GET { - return Err(HandshakeError::GetMethodRequired); - } - - // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) { - if let Ok(s) = hdr.to_str() { - s.to_ascii_lowercase().contains("websocket") - } else { - false - } - } else { - false - }; - if !has_hdr { - return Err(HandshakeError::NoWebsocketUpgrade); - } - - // Upgrade connection - if !req.upgrade() { - return Err(HandshakeError::NoConnectionUpgrade); - } - - // check supported version - if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) { - return Err(HandshakeError::NoVersionHeader); - } - let supported_ver = { - if let Some(hdr) = req.headers().get(header::SEC_WEBSOCKET_VERSION) { - hdr == "13" || hdr == "8" || hdr == "7" - } else { - false - } - }; - if !supported_ver { - return Err(HandshakeError::UnsupportedVersion); - } - - // check client handshake for validity - if !req.headers().contains_key(header::SEC_WEBSOCKET_KEY) { - return Err(HandshakeError::BadWebsocketKey); - } - Ok(()) -} - -/// Create websocket's handshake response -/// -/// This function returns handshake `Response`, ready to send to peer. -pub fn handshake_response(req: &RequestHead) -> ResponseBuilder { - let key = { - let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap(); - proto::hash_key(key.as_ref()) - }; - - Response::build(StatusCode::SWITCHING_PROTOCOLS) - .upgrade("websocket") - .header(header::TRANSFER_ENCODING, "chunked") - .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) - .take() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test::TestRequest; - use http::{header, Method}; - - #[test] - fn test_handshake() { - let req = TestRequest::default().method(Method::POST).finish(); - assert_eq!( - HandshakeError::GetMethodRequired, - verify_handshake(req.head()).err().unwrap() - ); - - let req = TestRequest::default().finish(); - assert_eq!( - HandshakeError::NoWebsocketUpgrade, - verify_handshake(req.head()).err().unwrap() - ); - - let req = TestRequest::default() - .header(header::UPGRADE, header::HeaderValue::from_static("test")) - .finish(); - assert_eq!( - HandshakeError::NoWebsocketUpgrade, - verify_handshake(req.head()).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .finish(); - assert_eq!( - HandshakeError::NoConnectionUpgrade, - verify_handshake(req.head()).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ) - .finish(); - assert_eq!( - HandshakeError::NoVersionHeader, - verify_handshake(req.head()).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ) - .header( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("5"), - ) - .finish(); - assert_eq!( - HandshakeError::UnsupportedVersion, - verify_handshake(req.head()).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ) - .header( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13"), - ) - .finish(); - assert_eq!( - HandshakeError::BadWebsocketKey, - verify_handshake(req.head()).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ) - .header( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13"), - ) - .header( - header::SEC_WEBSOCKET_KEY, - header::HeaderValue::from_static("13"), - ) - .finish(); - assert_eq!( - StatusCode::SWITCHING_PROTOCOLS, - handshake_response(req.head()).finish().status() - ); - } - - #[test] - fn test_wserror_http_response() { - let resp: Response = HandshakeError::GetMethodRequired.error_response(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let resp: Response = HandshakeError::NoWebsocketUpgrade.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::NoConnectionUpgrade.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::NoVersionHeader.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::UnsupportedVersion.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = HandshakeError::BadWebsocketKey.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } -} diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs deleted file mode 100644 index 60af6f08b..000000000 --- a/actix-http/src/ws/proto.rs +++ /dev/null @@ -1,322 +0,0 @@ -use base64; -use sha1; -use std::convert::{From, Into}; -use std::fmt; - -use self::OpCode::*; -/// Operation codes as part of rfc6455. -#[derive(Debug, Eq, PartialEq, Clone, Copy)] -pub enum OpCode { - /// Indicates a continuation frame of a fragmented message. - Continue, - /// Indicates a text data frame. - Text, - /// Indicates a binary data frame. - Binary, - /// Indicates a close control frame. - Close, - /// Indicates a ping control frame. - Ping, - /// Indicates a pong control frame. - Pong, - /// Indicates an invalid opcode was received. - Bad, -} - -impl fmt::Display for OpCode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Continue => write!(f, "CONTINUE"), - Text => write!(f, "TEXT"), - Binary => write!(f, "BINARY"), - Close => write!(f, "CLOSE"), - Ping => write!(f, "PING"), - Pong => write!(f, "PONG"), - Bad => write!(f, "BAD"), - } - } -} - -impl Into for OpCode { - fn into(self) -> u8 { - match self { - Continue => 0, - Text => 1, - Binary => 2, - Close => 8, - Ping => 9, - Pong => 10, - Bad => { - log::error!("Attempted to convert invalid opcode to u8. This is a bug."); - 8 // if this somehow happens, a close frame will help us tear down quickly - } - } - } -} - -impl From for OpCode { - fn from(byte: u8) -> OpCode { - match byte { - 0 => Continue, - 1 => Text, - 2 => Binary, - 8 => Close, - 9 => Ping, - 10 => Pong, - _ => Bad, - } - } -} - -use self::CloseCode::*; -/// Status code used to indicate why an endpoint is closing the `WebSocket` -/// connection. -#[derive(Debug, Eq, PartialEq, Clone, Copy)] -pub enum CloseCode { - /// Indicates a normal closure, meaning that the purpose for - /// which the connection was established has been fulfilled. - Normal, - /// Indicates that an endpoint is "going away", such as a server - /// going down or a browser having navigated away from a page. - Away, - /// Indicates that an endpoint is terminating the connection due - /// to a protocol error. - Protocol, - /// Indicates that an endpoint is terminating the connection - /// because it has received a type of data it cannot accept (e.g., an - /// endpoint that understands only text data MAY send this if it - /// receives a binary message). - Unsupported, - /// Indicates an abnormal closure. If the abnormal closure was due to an - /// error, this close code will not be used. Instead, the `on_error` method - /// of the handler will be called with the error. However, if the connection - /// is simply dropped, without an error, this close code will be sent to the - /// handler. - Abnormal, - /// Indicates that an endpoint is terminating the connection - /// because it has received data within a message that was not - /// consistent with the type of the message (e.g., non-UTF-8 \[RFC3629\] - /// data within a text message). - Invalid, - /// Indicates that an endpoint is terminating the connection - /// because it has received a message that violates its policy. This - /// is a generic status code that can be returned when there is no - /// other more suitable status code (e.g., Unsupported or Size) or if there - /// is a need to hide specific details about the policy. - Policy, - /// Indicates that an endpoint is terminating the connection - /// because it has received a message that is too big for it to - /// process. - Size, - /// Indicates that an endpoint (client) is terminating the - /// connection because it has expected the server to negotiate one or - /// more extension, but the server didn't return them in the response - /// message of the WebSocket handshake. The list of extensions that - /// are needed should be given as the reason for closing. - /// Note that this status code is not used by the server, because it - /// can fail the WebSocket handshake instead. - Extension, - /// Indicates that a server is terminating the connection because - /// it encountered an unexpected condition that prevented it from - /// fulfilling the request. - Error, - /// Indicates that the server is restarting. A client may choose to - /// reconnect, and if it does, it should use a randomized delay of 5-30 - /// seconds between attempts. - Restart, - /// Indicates that the server is overloaded and the client should either - /// connect to a different IP (when multiple targets exist), or - /// reconnect to the same IP when a user has performed an action. - Again, - #[doc(hidden)] - Tls, - #[doc(hidden)] - Other(u16), -} - -impl Into for CloseCode { - fn into(self) -> u16 { - match self { - Normal => 1000, - Away => 1001, - Protocol => 1002, - Unsupported => 1003, - Abnormal => 1006, - Invalid => 1007, - Policy => 1008, - Size => 1009, - Extension => 1010, - Error => 1011, - Restart => 1012, - Again => 1013, - Tls => 1015, - Other(code) => code, - } - } -} - -impl From for CloseCode { - fn from(code: u16) -> CloseCode { - match code { - 1000 => Normal, - 1001 => Away, - 1002 => Protocol, - 1003 => Unsupported, - 1006 => Abnormal, - 1007 => Invalid, - 1008 => Policy, - 1009 => Size, - 1010 => Extension, - 1011 => Error, - 1012 => Restart, - 1013 => Again, - 1015 => Tls, - _ => Other(code), - } - } -} - -#[derive(Debug, Eq, PartialEq, Clone)] -/// Reason for closing the connection -pub struct CloseReason { - /// Exit code - pub code: CloseCode, - /// Optional description of the exit code - pub description: Option, -} - -impl From for CloseReason { - fn from(code: CloseCode) -> Self { - CloseReason { - code, - description: None, - } - } -} - -impl> From<(CloseCode, T)> for CloseReason { - fn from(info: (CloseCode, T)) -> Self { - CloseReason { - code: info.0, - description: Some(info.1.into()), - } - } -} - -static WS_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - -// TODO: hash is always same size, we dont need String -pub fn hash_key(key: &[u8]) -> String { - use sha1::Digest; - let mut hasher = sha1::Sha1::new(); - - hasher.input(key); - hasher.input(WS_GUID.as_bytes()); - - base64::encode(hasher.result().as_ref()) -} - -#[cfg(test)] -mod test { - #![allow(unused_imports, unused_variables, dead_code)] - use super::*; - - macro_rules! opcode_into { - ($from:expr => $opcode:pat) => { - match OpCode::from($from) { - e @ $opcode => (), - e => unreachable!("{:?}", e), - } - }; - } - - macro_rules! opcode_from { - ($from:expr => $opcode:pat) => { - let res: u8 = $from.into(); - match res { - e @ $opcode => (), - e => unreachable!("{:?}", e), - } - }; - } - - #[test] - fn test_to_opcode() { - opcode_into!(0 => OpCode::Continue); - opcode_into!(1 => OpCode::Text); - opcode_into!(2 => OpCode::Binary); - opcode_into!(8 => OpCode::Close); - opcode_into!(9 => OpCode::Ping); - opcode_into!(10 => OpCode::Pong); - opcode_into!(99 => OpCode::Bad); - } - - #[test] - fn test_from_opcode() { - opcode_from!(OpCode::Continue => 0); - opcode_from!(OpCode::Text => 1); - opcode_from!(OpCode::Binary => 2); - opcode_from!(OpCode::Close => 8); - opcode_from!(OpCode::Ping => 9); - opcode_from!(OpCode::Pong => 10); - } - - #[test] - #[should_panic] - fn test_from_opcode_debug() { - opcode_from!(OpCode::Bad => 99); - } - - #[test] - fn test_from_opcode_display() { - assert_eq!(format!("{}", OpCode::Continue), "CONTINUE"); - assert_eq!(format!("{}", OpCode::Text), "TEXT"); - assert_eq!(format!("{}", OpCode::Binary), "BINARY"); - assert_eq!(format!("{}", OpCode::Close), "CLOSE"); - assert_eq!(format!("{}", OpCode::Ping), "PING"); - assert_eq!(format!("{}", OpCode::Pong), "PONG"); - assert_eq!(format!("{}", OpCode::Bad), "BAD"); - } - - #[test] - fn test_hash_key() { - let hash = hash_key(b"hello actix-web"); - assert_eq!(&hash, "cR1dlyUUJKp0s/Bel25u5TgvC3E="); - } - - #[test] - fn closecode_from_u16() { - assert_eq!(CloseCode::from(1000u16), CloseCode::Normal); - assert_eq!(CloseCode::from(1001u16), CloseCode::Away); - assert_eq!(CloseCode::from(1002u16), CloseCode::Protocol); - assert_eq!(CloseCode::from(1003u16), CloseCode::Unsupported); - assert_eq!(CloseCode::from(1006u16), CloseCode::Abnormal); - assert_eq!(CloseCode::from(1007u16), CloseCode::Invalid); - assert_eq!(CloseCode::from(1008u16), CloseCode::Policy); - assert_eq!(CloseCode::from(1009u16), CloseCode::Size); - assert_eq!(CloseCode::from(1010u16), CloseCode::Extension); - assert_eq!(CloseCode::from(1011u16), CloseCode::Error); - assert_eq!(CloseCode::from(1012u16), CloseCode::Restart); - assert_eq!(CloseCode::from(1013u16), CloseCode::Again); - assert_eq!(CloseCode::from(1015u16), CloseCode::Tls); - assert_eq!(CloseCode::from(2000u16), CloseCode::Other(2000)); - } - - #[test] - fn closecode_into_u16() { - assert_eq!(1000u16, Into::::into(CloseCode::Normal)); - assert_eq!(1001u16, Into::::into(CloseCode::Away)); - assert_eq!(1002u16, Into::::into(CloseCode::Protocol)); - assert_eq!(1003u16, Into::::into(CloseCode::Unsupported)); - assert_eq!(1006u16, Into::::into(CloseCode::Abnormal)); - assert_eq!(1007u16, Into::::into(CloseCode::Invalid)); - assert_eq!(1008u16, Into::::into(CloseCode::Policy)); - assert_eq!(1009u16, Into::::into(CloseCode::Size)); - assert_eq!(1010u16, Into::::into(CloseCode::Extension)); - assert_eq!(1011u16, Into::::into(CloseCode::Error)); - assert_eq!(1012u16, Into::::into(CloseCode::Restart)); - assert_eq!(1013u16, Into::::into(CloseCode::Again)); - assert_eq!(1015u16, Into::::into(CloseCode::Tls)); - assert_eq!(2000u16, Into::::into(CloseCode::Other(2000))); - } -} diff --git a/actix-http/tests/test.binary b/actix-http/tests/test.binary deleted file mode 100644 index ef8ff0245..000000000 --- a/actix-http/tests/test.binary +++ /dev/null @@ -1 +0,0 @@ -ÂTÇ‘É‚Vù2þvI ª–\ÇRË™–ˆæeÞvDØ:è—½¬RVÖYpíÿ;ÍÏGñùp!2÷CŒ.– û®õpA !ûߦÙx j+Uc÷±©X”c%Û;ï"yì­AI \ No newline at end of file diff --git a/actix-http/tests/test.png b/actix-http/tests/test.png deleted file mode 100644 index 6b7cdc0b8..000000000 Binary files a/actix-http/tests/test.png and /dev/null differ diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs deleted file mode 100644 index 9da3b04a2..000000000 --- a/actix-http/tests/test_client.rs +++ /dev/null @@ -1,88 +0,0 @@ -use actix_service::ServiceFactory; -use bytes::Bytes; -use futures::future::{self, ok}; - -use actix_http::{http, HttpService, Request, Response}; -use actix_http_test::test_server; - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[actix_rt::test] -async fn test_h1_v2() { - let srv = test_server(move || { - HttpService::build() - .finish(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .tcp() - }); - - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - - let request = srv.get("/").header("x-test", "111").send(); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - - let mut response = srv.post("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_connection_close() { - let srv = test_server(move || { - HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .tcp() - .map(|_| ()) - }); - - let response = srv.get("/").force_close().send().await.unwrap(); - assert!(response.status().is_success()); -} - -#[actix_rt::test] -async fn test_with_query_parameter() { - let srv = test_server(move || { - HttpService::build() - .finish(|req: Request| { - if req.uri().query().unwrap().contains("qp=") { - ok::<_, ()>(Response::Ok().finish()) - } else { - ok::<_, ()>(Response::BadRequest().finish()) - } - }) - .tcp() - .map(|_| ()) - }); - - let request = srv.request(http::Method::GET, srv.url("/?qp=5")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); -} diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs deleted file mode 100644 index b25f05272..000000000 --- a/actix-http/tests/test_openssl.rs +++ /dev/null @@ -1,416 +0,0 @@ -#![cfg(feature = "openssl")] -use std::io; - -use actix_http_test::test_server; -use actix_service::{fn_service, ServiceFactory}; - -use bytes::{Bytes, BytesMut}; -use futures::future::{err, ok, ready}; -use futures::stream::{once, Stream, StreamExt}; -use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; - -use actix_http::error::{ErrorBadRequest, PayloadError}; -use actix_http::http::header::{self, HeaderName, HeaderValue}; -use actix_http::http::{Method, StatusCode, Version}; -use actix_http::httpmessage::HttpMessage; -use actix_http::{body, Error, HttpService, Request, Response}; - -async fn load_body(stream: S) -> Result -where - S: Stream>, -{ - let body = stream - .map(|res| match res { - Ok(chunk) => chunk, - Err(_) => panic!(), - }) - .fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - ready(body) - }) - .await; - - Ok(body) -} - -fn ssl_acceptor() -> SslAcceptor { - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - const H11: &[u8] = b"\x08http/1.1"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else if protos.windows(9).any(|window| window == H11) { - Ok(b"http/1.1") - } else { - Err(AlpnError::NOACK) - } - }); - builder - .set_alpn_protos(b"\x08http/1.1\x02h2") - .expect("Can not contrust SslAcceptor"); - - builder.build() -} - -#[actix_rt::test] -async fn test_h2() -> io::Result<()> { - let srv = test_server(move || { - HttpService::build() - .h2(|_| ok::<_, Error>(Response::Ok().finish())) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[actix_rt::test] -async fn test_h2_1() -> io::Result<()> { - let srv = test_server(move || { - HttpService::build() - .finish(|req: Request| { - assert!(req.peer_addr().is_some()); - assert_eq!(req.version(), Version::HTTP_2); - ok::<_, Error>(Response::Ok().finish()) - }) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[actix_rt::test] -async fn test_h2_body() -> io::Result<()> { - let data = "HELLOWORLD".to_owned().repeat(64 * 1024); - let mut srv = test_server(move || { - HttpService::build() - .h2(|mut req: Request<_>| { - async move { - let body = load_body(req.take_payload()).await?; - Ok::<_, Error>(Response::Ok().body(body)) - } - }) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.sget("/").send_body(data.clone()).await.unwrap(); - assert!(response.status().is_success()); - - let body = srv.load_body(response).await.unwrap(); - assert_eq!(&body, data.as_bytes()); - Ok(()) -} - -#[actix_rt::test] -async fn test_h2_content_length() { - let srv = test_server(move || { - HttpService::build() - .h2(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - ok::<_, ()>(Response::new(statuses[indx])) - }) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); - - { - for i in 0..4 { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); - - let req = srv - .request(Method::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); - } - - for i in 4..6 { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), Some(&value)); - } - } -} - -#[actix_rt::test] -async fn test_h2_headers() { - let data = STR.repeat(10); - let data2 = data.clone(); - - let mut srv = test_server(move || { - let data = data.clone(); - HttpService::build().h2(move |_| { - let mut builder = Response::Ok(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - ok::<_, ()>(builder.body(data.clone())) - }) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from(data2)); -} - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[actix_rt::test] -async fn test_h2_body2() { - let mut srv = test_server(move || { - HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_h2_head_empty() { - let mut srv = test_server(move || { - HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.shead("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); - - { - let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert!(bytes.is_empty()); -} - -#[actix_rt::test] -async fn test_h2_head_binary() { - let mut srv = test_server(move || { - HttpService::build() - .h2(|_| { - ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) - }) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.shead("/").send().await.unwrap(); - assert!(response.status().is_success()); - - { - let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert!(bytes.is_empty()); -} - -#[actix_rt::test] -async fn test_h2_head_binary2() { - let srv = test_server(move || { - HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.shead("/").send().await.unwrap(); - assert!(response.status().is_success()); - - { - let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } -} - -#[actix_rt::test] -async fn test_h2_body_length() { - let mut srv = test_server(move || { - HttpService::build() - .h2(|_| { - let body = once(ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), - ) - }) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_h2_body_chunked_explicit() { - let mut srv = test_server(move || { - HttpService::build() - .h2(|_| { - let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") - .streaming(body), - ) - }) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - - // decode - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_h2_response_http_error_handling() { - let mut srv = test_server(move || { - HttpService::build() - .h2(fn_service(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header(header::CONTENT_TYPE, broken_header) - .body(STR), - ) - })) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); -} - -#[actix_rt::test] -async fn test_h2_service_error() { - let mut srv = test_server(move || { - HttpService::build() - .h2(|_| err::(ErrorBadRequest("error"))) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"error")); -} - -#[actix_rt::test] -async fn test_h2_on_connect() { - let srv = test_server(move || { - HttpService::build() - .on_connect(|_| 10usize) - .h2(|req: Request| { - assert!(req.extensions().contains::()); - ok::<_, ()>(Response::Ok().finish()) - }) - .openssl(ssl_acceptor()) - .map_err(|_| ()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); -} diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs deleted file mode 100644 index bc0c91cc3..000000000 --- a/actix-http/tests/test_rustls.rs +++ /dev/null @@ -1,421 +0,0 @@ -#![cfg(feature = "rustls")] -use actix_http::error::PayloadError; -use actix_http::http::header::{self, HeaderName, HeaderValue}; -use actix_http::http::{Method, StatusCode, Version}; -use actix_http::{body, error, Error, HttpService, Request, Response}; -use actix_http_test::test_server; -use actix_service::{fn_factory_with_config, fn_service}; - -use bytes::{Bytes, BytesMut}; -use futures::future::{self, err, ok}; -use futures::stream::{once, Stream, StreamExt}; -use rust_tls::{ - internal::pemfile::{certs, pkcs8_private_keys}, - NoClientAuth, ServerConfig as RustlsServerConfig, -}; - -use std::fs::File; -use std::io::{self, BufReader}; - -async fn load_body(mut stream: S) -> Result -where - S: Stream> + Unpin, -{ - let mut body = BytesMut::new(); - while let Some(item) = stream.next().await { - body.extend_from_slice(&item?) - } - Ok(body) -} - -fn ssl_acceptor() -> RustlsServerConfig { - // load ssl keys - let mut config = RustlsServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("../tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("../tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - config -} - -#[actix_rt::test] -async fn test_h1() -> io::Result<()> { - let srv = test_server(move || { - HttpService::build() - .h1(|_| future::ok::<_, Error>(Response::Ok().finish())) - .rustls(ssl_acceptor()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[actix_rt::test] -async fn test_h2() -> io::Result<()> { - let srv = test_server(move || { - HttpService::build() - .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) - .rustls(ssl_acceptor()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[actix_rt::test] -async fn test_h1_1() -> io::Result<()> { - let srv = test_server(move || { - HttpService::build() - .h1(|req: Request| { - assert!(req.peer_addr().is_some()); - assert_eq!(req.version(), Version::HTTP_11); - future::ok::<_, Error>(Response::Ok().finish()) - }) - .rustls(ssl_acceptor()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[actix_rt::test] -async fn test_h2_1() -> io::Result<()> { - let srv = test_server(move || { - HttpService::build() - .finish(|req: Request| { - assert!(req.peer_addr().is_some()); - assert_eq!(req.version(), Version::HTTP_2); - future::ok::<_, Error>(Response::Ok().finish()) - }) - .rustls(ssl_acceptor()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[actix_rt::test] -async fn test_h2_body1() -> io::Result<()> { - let data = "HELLOWORLD".to_owned().repeat(64 * 1024); - let mut srv = test_server(move || { - HttpService::build() - .h2(|mut req: Request<_>| { - async move { - let body = load_body(req.take_payload()).await?; - Ok::<_, Error>(Response::Ok().body(body)) - } - }) - .rustls(ssl_acceptor()) - }); - - let response = srv.sget("/").send_body(data.clone()).await.unwrap(); - assert!(response.status().is_success()); - - let body = srv.load_body(response).await.unwrap(); - assert_eq!(&body, data.as_bytes()); - Ok(()) -} - -#[actix_rt::test] -async fn test_h2_content_length() { - let srv = test_server(move || { - HttpService::build() - .h2(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - future::ok::<_, ()>(Response::new(statuses[indx])) - }) - .rustls(ssl_acceptor()) - }); - - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); - { - for i in 0..4 { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); - - let req = srv - .request(Method::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), None); - } - - for i in 4..6 { - let req = srv - .request(Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = req.await.unwrap(); - assert_eq!(response.headers().get(&header), Some(&value)); - } - } -} - -#[actix_rt::test] -async fn test_h2_headers() { - let data = STR.repeat(10); - let data2 = data.clone(); - - let mut srv = test_server(move || { - let data = data.clone(); - HttpService::build().h2(move |_| { - let mut config = Response::Ok(); - for idx in 0..90 { - config.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - future::ok::<_, ()>(config.body(data.clone())) - }) - .rustls(ssl_acceptor()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from(data2)); -} - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[actix_rt::test] -async fn test_h2_body2() { - let mut srv = test_server(move || { - HttpService::build() - .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .rustls(ssl_acceptor()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_h2_head_empty() { - let mut srv = test_server(move || { - HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .rustls(ssl_acceptor()) - }); - - let response = srv.shead("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert!(bytes.is_empty()); -} - -#[actix_rt::test] -async fn test_h2_head_binary() { - let mut srv = test_server(move || { - HttpService::build() - .h2(|_| { - ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) - }) - .rustls(ssl_acceptor()) - }); - - let response = srv.shead("/").send().await.unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert!(bytes.is_empty()); -} - -#[actix_rt::test] -async fn test_h2_head_binary2() { - let srv = test_server(move || { - HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .rustls(ssl_acceptor()) - }); - - let response = srv.shead("/").send().await.unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } -} - -#[actix_rt::test] -async fn test_h2_body_length() { - let mut srv = test_server(move || { - HttpService::build() - .h2(|_| { - let body = once(ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), - ) - }) - .rustls(ssl_acceptor()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_h2_body_chunked_explicit() { - let mut srv = test_server(move || { - HttpService::build() - .h2(|_| { - let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") - .streaming(body), - ) - }) - .rustls(ssl_acceptor()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - - // decode - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_h2_response_http_error_handling() { - let mut srv = test_server(move || { - HttpService::build() - .h2(fn_factory_with_config(|_: ()| { - ok::<_, ()>(fn_service(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) - .body(STR), - ) - })) - })) - .rustls(ssl_acceptor()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); -} - -#[actix_rt::test] -async fn test_h2_service_error() { - let mut srv = test_server(move || { - HttpService::build() - .h2(|_| err::(error::ErrorBadRequest("error"))) - .rustls(ssl_acceptor()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"error")); -} - -#[actix_rt::test] -async fn test_h1_service_error() { - let mut srv = test_server(move || { - HttpService::build() - .h1(|_| err::(error::ErrorBadRequest("error"))) - .rustls(ssl_acceptor()) - }); - - let response = srv.sget("/").send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"error")); -} diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs deleted file mode 100644 index a84692f9d..000000000 --- a/actix-http/tests/test_server.rs +++ /dev/null @@ -1,654 +0,0 @@ -use std::io::{Read, Write}; -use std::time::Duration; -use std::{net, thread}; - -use actix_http_test::test_server; -use actix_rt::time::delay_for; -use actix_service::fn_service; -use bytes::Bytes; -use futures::future::{self, err, ok, ready, FutureExt}; -use futures::stream::{once, StreamExt}; -use regex::Regex; - -use actix_http::httpmessage::HttpMessage; -use actix_http::{ - body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, -}; - -#[actix_rt::test] -async fn test_h1() { - let srv = test_server(|| { - HttpService::build() - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) - .h1(|req: Request| { - assert!(req.peer_addr().is_some()); - future::ok::<_, ()>(Response::Ok().finish()) - }) - .tcp() - }); - - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); -} - -#[actix_rt::test] -async fn test_h1_2() { - let srv = test_server(|| { - HttpService::build() - .keep_alive(KeepAlive::Disabled) - .client_timeout(1000) - .client_disconnect(1000) - .finish(|req: Request| { - assert!(req.peer_addr().is_some()); - assert_eq!(req.version(), http::Version::HTTP_11); - future::ok::<_, ()>(Response::Ok().finish()) - }) - .tcp() - }); - - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); -} - -#[actix_rt::test] -async fn test_expect_continue() { - let srv = test_server(|| { - HttpService::build() - .expect(fn_service(|req: Request| { - if req.head().uri.query() == Some("yes=") { - ok(req) - } else { - err(error::ErrorPreconditionFailed("error")) - } - })) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .tcp() - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); -} - -#[actix_rt::test] -async fn test_expect_continue_h1() { - let srv = test_server(|| { - HttpService::build() - .expect(fn_service(|req: Request| { - delay_for(Duration::from_millis(20)).then(move |_| { - if req.head().uri.query() == Some("yes=") { - ok(req) - } else { - err(error::ErrorPreconditionFailed("error")) - } - }) - })) - .h1(fn_service(|_| future::ok::<_, ()>(Response::Ok().finish()))) - .tcp() - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length")); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n")); -} - -#[actix_rt::test] -async fn test_chunked_payload() { - let chunk_sizes = vec![32768, 32, 32768]; - let total_size: usize = chunk_sizes.iter().sum(); - - let srv = test_server(|| { - HttpService::build() - .h1(fn_service(|mut request: Request| { - request - .take_payload() - .map(|res| match res { - Ok(pl) => pl, - Err(e) => panic!(format!("Error reading payload: {}", e)), - }) - .fold(0usize, |acc, chunk| ready(acc + chunk.len())) - .map(|req_size| { - Ok::<_, Error>(Response::Ok().body(format!("size={}", req_size))) - }) - })) - .tcp() - }); - - let returned_size = { - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream - .write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"); - - for chunk_size in chunk_sizes.iter() { - let mut bytes = Vec::new(); - let random_bytes: Vec = - (0..*chunk_size).map(|_| rand::random::()).collect(); - - bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes()); - bytes.extend(&random_bytes[..]); - bytes.extend(b"\r\n"); - let _ = stream.write_all(&bytes); - } - - let _ = stream.write_all(b"0\r\n\r\n"); - stream.shutdown(net::Shutdown::Write).unwrap(); - - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - - let re = Regex::new(r"size=(\d+)").unwrap(); - let size: usize = match re.captures(&data) { - Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(), - None => panic!(format!("Failed to find size in HTTP Response: {}", data)), - }; - size - }; - - assert_eq!(returned_size, total_size); -} - -#[actix_rt::test] -async fn test_slow_request() { - let srv = test_server(|| { - HttpService::build() - .client_timeout(100) - .finish(|_| future::ok::<_, ()>(Response::Ok().finish())) - .tcp() - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); -} - -#[actix_rt::test] -async fn test_http1_malformed_request() { - let srv = test_server(|| { - HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - .tcp() - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 400 Bad Request")); -} - -#[actix_rt::test] -async fn test_http1_keepalive() { - let srv = test_server(|| { - HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - .tcp() - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); -} - -#[actix_rt::test] -async fn test_http1_keepalive_timeout() { - let srv = test_server(|| { - HttpService::build() - .keep_alive(1) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - .tcp() - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - thread::sleep(Duration::from_millis(1100)); - - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); -} - -#[actix_rt::test] -async fn test_http1_keepalive_close() { - let srv = test_server(|| { - HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - .tcp() - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = - stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); -} - -#[actix_rt::test] -async fn test_http10_keepalive_default_close() { - let srv = test_server(|| { - HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - .tcp() - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); - - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); -} - -#[actix_rt::test] -async fn test_http10_keepalive() { - let srv = test_server(|| { - HttpService::build() - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - .tcp() - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream - .write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n"); - - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); -} - -#[actix_rt::test] -async fn test_http1_keepalive_disabled() { - let srv = test_server(|| { - HttpService::build() - .keep_alive(KeepAlive::Disabled) - .h1(|_| future::ok::<_, ()>(Response::Ok().finish())) - .tcp() - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n\r\n"); - let mut data = vec![0; 1024]; - let _ = stream.read(&mut data); - assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n"); - - let mut data = vec![0; 1024]; - let res = stream.read(&mut data).unwrap(); - assert_eq!(res, 0); -} - -#[actix_rt::test] -async fn test_content_length() { - use actix_http::http::{ - header::{HeaderName, HeaderValue}, - StatusCode, - }; - - let srv = test_server(|| { - HttpService::build() - .h1(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - future::ok::<_, ()>(Response::new(statuses[indx])) - }) - .tcp() - }); - - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); - - { - for i in 0..4 { - let req = srv.request(http::Method::GET, srv.url(&format!("/{}", i))); - let response = req.send().await.unwrap(); - assert_eq!(response.headers().get(&header), None); - - let req = srv.request(http::Method::HEAD, srv.url(&format!("/{}", i))); - let response = req.send().await.unwrap(); - assert_eq!(response.headers().get(&header), None); - } - - for i in 4..6 { - let req = srv.request(http::Method::GET, srv.url(&format!("/{}", i))); - let response = req.send().await.unwrap(); - assert_eq!(response.headers().get(&header), Some(&value)); - } - } -} - -#[actix_rt::test] -async fn test_h1_headers() { - let data = STR.repeat(10); - let data2 = data.clone(); - - let mut srv = test_server(move || { - let data = data.clone(); - HttpService::build().h1(move |_| { - let mut builder = Response::Ok(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - future::ok::<_, ()>(builder.body(data.clone())) - }).tcp() - }); - - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from(data2)); -} - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[actix_rt::test] -async fn test_h1_body() { - let mut srv = test_server(|| { - HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().body(STR))) - .tcp() - }); - - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_h1_head_empty() { - let mut srv = test_server(|| { - HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().body(STR))) - .tcp() - }); - - let response = srv.head("/").send().await.unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert!(bytes.is_empty()); -} - -#[actix_rt::test] -async fn test_h1_head_binary() { - let mut srv = test_server(|| { - HttpService::build() - .h1(|_| { - ok::<_, ()>(Response::Ok().content_length(STR.len() as u64).body(STR)) - }) - .tcp() - }); - - let response = srv.head("/").send().await.unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert!(bytes.is_empty()); -} - -#[actix_rt::test] -async fn test_h1_head_binary2() { - let srv = test_server(|| { - HttpService::build() - .h1(|_| ok::<_, ()>(Response::Ok().body(STR))) - .tcp() - }); - - let response = srv.head("/").send().await.unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } -} - -#[actix_rt::test] -async fn test_h1_body_length() { - let mut srv = test_server(|| { - HttpService::build() - .h1(|_| { - let body = once(ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), - ) - }) - .tcp() - }); - - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_h1_body_chunked_explicit() { - let mut srv = test_server(|| { - HttpService::build() - .h1(|_| { - let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") - .streaming(body), - ) - }) - .tcp() - }); - - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!( - response - .headers() - .get(header::TRANSFER_ENCODING) - .unwrap() - .to_str() - .unwrap(), - "chunked" - ); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - - // decode - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_h1_body_chunked_implicit() { - let mut srv = test_server(|| { - HttpService::build() - .h1(|_| { - let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>(Response::Ok().streaming(body)) - }) - .tcp() - }); - - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!( - response - .headers() - .get(header::TRANSFER_ENCODING) - .unwrap() - .to_str() - .unwrap(), - "chunked" - ); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_h1_response_http_error_handling() { - let mut srv = test_server(|| { - HttpService::build() - .h1(fn_service(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) - .body(STR), - ) - })) - .tcp() - }); - - let response = srv.get("/").send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); -} - -#[actix_rt::test] -async fn test_h1_service_error() { - let mut srv = test_server(|| { - HttpService::build() - .h1(|_| future::err::(error::ErrorBadRequest("error"))) - .tcp() - }); - - let response = srv.get("/").send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); - - // read response - let bytes = srv.load_body(response).await.unwrap(); - assert_eq!(bytes, Bytes::from_static(b"error")); -} - -#[actix_rt::test] -async fn test_h1_on_connect() { - let srv = test_server(|| { - HttpService::build() - .on_connect(|_| 10usize) - .h1(|req: Request| { - assert!(req.extensions().contains::()); - future::ok::<_, ()>(Response::Ok().finish()) - }) - .tcp() - }); - - let response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); -} diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs deleted file mode 100644 index 7152fee48..000000000 --- a/actix-http/tests/test_ws.rs +++ /dev/null @@ -1,194 +0,0 @@ -use std::cell::Cell; -use std::marker::PhantomData; -use std::pin::Pin; -use std::sync::{Arc, Mutex}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_http::{body, h1, ws, Error, HttpService, Request, Response}; -use actix_http_test::test_server; -use actix_service::{fn_factory, Service}; -use actix_utils::framed::Dispatcher; -use bytes::Bytes; -use futures::future; -use futures::task::{Context, Poll}; -use futures::{Future, SinkExt, StreamExt}; - -struct WsService(Arc, Cell)>>); - -impl WsService { - fn new() -> Self { - WsService(Arc::new(Mutex::new((PhantomData, Cell::new(false))))) - } - - fn set_polled(&mut self) { - *self.0.lock().unwrap().1.get_mut() = true; - } - - fn was_polled(&self) -> bool { - self.0.lock().unwrap().1.get() - } -} - -impl Clone for WsService { - fn clone(&self) -> Self { - WsService(self.0.clone()) - } -} - -impl Service for WsService -where - T: AsyncRead + AsyncWrite + Unpin + 'static, -{ - type Request = (Request, Framed); - type Response = (); - type Error = Error; - type Future = Pin>>>; - - fn poll_ready(&mut self, _ctx: &mut Context<'_>) -> Poll> { - self.set_polled(); - Poll::Ready(Ok(())) - } - - fn call(&mut self, (req, mut framed): Self::Request) -> Self::Future { - let fut = async move { - let res = ws::handshake(req.head()).unwrap().message_body(()); - - framed - .send((res, body::BodySize::None).into()) - .await - .unwrap(); - - Dispatcher::new(framed.into_framed(ws::Codec::new()), service) - .await - .map_err(|_| panic!()) - }; - - Box::pin(fut) - } -} - -async fn service(msg: ws::Frame) -> Result { - let msg = match msg { - ws::Frame::Ping(msg) => ws::Message::Pong(msg), - ws::Frame::Text(text) => { - ws::Message::Text(String::from_utf8_lossy(&text).to_string()) - } - ws::Frame::Binary(bin) => ws::Message::Binary(bin), - ws::Frame::Continuation(item) => ws::Message::Continuation(item), - ws::Frame::Close(reason) => ws::Message::Close(reason), - _ => panic!(), - }; - Ok(msg) -} - -#[actix_rt::test] -async fn test_simple() { - let ws_service = WsService::new(); - let mut srv = test_server({ - let ws_service = ws_service.clone(); - move || { - let ws_service = ws_service.clone(); - HttpService::build() - .upgrade(fn_factory(move || future::ok::<_, ()>(ws_service.clone()))) - .finish(|_| future::ok::<_, ()>(Response::NotFound())) - .tcp() - } - }); - - // client service - let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Text(Bytes::from_static(b"text")) - ); - - framed - .send(ws::Message::Binary("text".into())) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Binary(Bytes::from_static(&b"text"[..])) - ); - - framed.send(ws::Message::Ping("text".into())).await.unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Pong("text".to_string().into()) - ); - - framed - .send(ws::Message::Continuation(ws::Item::FirstText( - "text".into(), - ))) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Continuation(ws::Item::FirstText(Bytes::from_static(b"text"))) - ); - - assert!(framed - .send(ws::Message::Continuation(ws::Item::FirstText( - "text".into() - ))) - .await - .is_err()); - assert!(framed - .send(ws::Message::Continuation(ws::Item::FirstBinary( - "text".into() - ))) - .await - .is_err()); - - framed - .send(ws::Message::Continuation(ws::Item::Continue("text".into()))) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Continuation(ws::Item::Continue(Bytes::from_static(b"text"))) - ); - - framed - .send(ws::Message::Continuation(ws::Item::Last("text".into()))) - .await - .unwrap(); - let (item, mut framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Continuation(ws::Item::Last(Bytes::from_static(b"text"))) - ); - - assert!(framed - .send(ws::Message::Continuation(ws::Item::Continue("text".into()))) - .await - .is_err()); - - assert!(framed - .send(ws::Message::Continuation(ws::Item::Last("text".into()))) - .await - .is_err()); - - framed - .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - .await - .unwrap(); - - let (item, _framed) = framed.into_future().await; - assert_eq!( - item.unwrap().unwrap(), - ws::Frame::Close(Some(ws::CloseCode::Normal.into())) - ); - - assert!(ws_service.was_polled()); -} diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md deleted file mode 100644 index d73a69393..000000000 --- a/actix-multipart/CHANGES.md +++ /dev/null @@ -1,51 +0,0 @@ -# Changes - -## [0.2.1] - 2020-01-xx - -* Remove the unused `time` dependency - -## [0.2.0] - 2019-12-20 - -* Release - -## [0.2.0-alpha.4] - 2019-12-xx - -* Multipart handling now handles Pending during read of boundary #1205 - -## [0.2.0-alpha.2] - 2019-12-03 - -* Migrate to `std::future` - -## [0.1.4] - 2019-09-12 - -* Multipart handling now parses requests which do not end in CRLF #1038 - -## [0.1.3] - 2019-08-18 - -* Fix ring dependency from actix-web default features for #741. - -## [0.1.2] - 2019-06-02 - -* Fix boundary parsing #876 - -## [0.1.1] - 2019-05-25 - -* Fix disconnect handling #834 - -## [0.1.0] - 2019-05-18 - -* Release - -## [0.1.0-beta.4] - 2019-05-12 - -* Handle cancellation of uploads #736 - -* Upgrade to actix-web 1.0.0-beta.4 - -## [0.1.0-beta.1] - 2019-04-21 - -* Do not support nested multipart - -* Split multipart support to separate crate - -* Optimize multipart handling #634, #769 diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml deleted file mode 100644 index f9cd7cfd2..000000000 --- a/actix-multipart/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "actix-multipart" -version = "0.2.0" -authors = ["Nikolay Kim "] -description = "Multipart support for actix web framework." -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-multipart/" -license = "MIT/Apache-2.0" -edition = "2018" - -[lib] -name = "actix_multipart" -path = "src/lib.rs" - -[dependencies] -actix-web = { version = "2.0.0-rc", default-features = false } -actix-service = "1.0.1" -actix-utils = "1.0.3" -bytes = "0.5.3" -derive_more = "0.99.2" -httparse = "1.3" -futures = "0.3.1" -log = "0.4" -mime = "0.3" -twoway = "0.2" - -[dev-dependencies] -actix-rt = "1.0.0" -actix-http = "1.0.0" diff --git a/actix-multipart/LICENSE-APACHE b/actix-multipart/LICENSE-APACHE deleted file mode 120000 index 965b606f3..000000000 --- a/actix-multipart/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/actix-multipart/LICENSE-MIT b/actix-multipart/LICENSE-MIT deleted file mode 120000 index 76219eb72..000000000 --- a/actix-multipart/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/actix-multipart/README.md b/actix-multipart/README.md deleted file mode 100644 index a453f489e..000000000 --- a/actix-multipart/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Multipart support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-multipart)](https://crates.io/crates/actix-multipart) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -## Documentation & community resources - -* [API Documentation](https://docs.rs/actix-multipart/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-multipart](https://crates.io/crates/actix-multipart) -* Minimum supported Rust version: 1.39 or later diff --git a/actix-multipart/src/error.rs b/actix-multipart/src/error.rs deleted file mode 100644 index 6677f69c7..000000000 --- a/actix-multipart/src/error.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Error and Result module -use actix_web::error::{ParseError, PayloadError}; -use actix_web::http::StatusCode; -use actix_web::ResponseError; -use derive_more::{Display, From}; - -/// A set of errors that can occur during parsing multipart streams -#[derive(Debug, Display, From)] -pub enum MultipartError { - /// Content-Type header is not found - #[display(fmt = "No Content-type header found")] - NoContentType, - /// Can not parse Content-Type header - #[display(fmt = "Can not parse Content-Type header")] - ParseContentType, - /// Multipart boundary is not found - #[display(fmt = "Multipart boundary is not found")] - Boundary, - /// Nested multipart is not supported - #[display(fmt = "Nested multipart is not supported")] - Nested, - /// Multipart stream is incomplete - #[display(fmt = "Multipart stream is incomplete")] - Incomplete, - /// Error during field parsing - #[display(fmt = "{}", _0)] - Parse(ParseError), - /// Payload error - #[display(fmt = "{}", _0)] - Payload(PayloadError), - /// Not consumed - #[display(fmt = "Multipart stream is not consumed")] - NotConsumed, -} - -/// Return `BadRequest` for `MultipartError` -impl ResponseError for MultipartError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - -#[cfg(test)] -mod tests { - use super::*; - use actix_web::HttpResponse; - - #[test] - fn test_multipart_error() { - let resp: HttpResponse = MultipartError::Boundary.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } -} diff --git a/actix-multipart/src/extractor.rs b/actix-multipart/src/extractor.rs deleted file mode 100644 index 71c815227..000000000 --- a/actix-multipart/src/extractor.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! Multipart payload support -use actix_web::{dev::Payload, Error, FromRequest, HttpRequest}; -use futures::future::{ok, Ready}; - -use crate::server::Multipart; - -/// Get request's payload as multipart stream -/// -/// Content-type: multipart/form-data; -/// -/// ## Server example -/// -/// ```rust -/// use futures::{Stream, StreamExt}; -/// use actix_web::{web, HttpResponse, Error}; -/// use actix_multipart as mp; -/// -/// async fn index(mut payload: mp::Multipart) -> Result { -/// // iterate over multipart stream -/// while let Some(item) = payload.next().await { -/// let mut field = item?; -/// -/// // Field in turn is stream of *Bytes* object -/// while let Some(chunk) = field.next().await { -/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk?)); -/// } -/// } -/// Ok(HttpResponse::Ok().into()) -/// } -/// # fn main() {} -/// ``` -impl FromRequest for Multipart { - type Error = Error; - type Future = Ready>; - type Config = (); - - #[inline] - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - ok(Multipart::new(req.headers(), payload.take())) - } -} diff --git a/actix-multipart/src/lib.rs b/actix-multipart/src/lib.rs deleted file mode 100644 index 43eb048ca..000000000 --- a/actix-multipart/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![allow(clippy::borrow_interior_mutable_const)] - -mod error; -mod extractor; -mod server; - -pub use self::error::MultipartError; -pub use self::server::{Field, Multipart}; diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs deleted file mode 100644 index 2555cb7a3..000000000 --- a/actix-multipart/src/server.rs +++ /dev/null @@ -1,1152 +0,0 @@ -//! Multipart payload support -use std::cell::{Cell, RefCell, RefMut}; -use std::convert::TryFrom; -use std::marker::PhantomData; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{cmp, fmt}; - -use bytes::{Bytes, BytesMut}; -use futures::stream::{LocalBoxStream, Stream, StreamExt}; -use httparse; -use mime; - -use actix_utils::task::LocalWaker; -use actix_web::error::{ParseError, PayloadError}; -use actix_web::http::header::{ - self, ContentDisposition, HeaderMap, HeaderName, HeaderValue, -}; - -use crate::error::MultipartError; - -const MAX_HEADERS: usize = 32; - -/// The server-side implementation of `multipart/form-data` requests. -/// -/// This will parse the incoming stream into `MultipartItem` instances via its -/// Stream implementation. -/// `MultipartItem::Field` contains multipart field. `MultipartItem::Multipart` -/// is used for nested multipart streams. -pub struct Multipart { - safety: Safety, - error: Option, - inner: Option>>, -} - -enum InnerMultipartItem { - None, - Field(Rc>), -} - -#[derive(PartialEq, Debug)] -enum InnerState { - /// Stream eof - Eof, - /// Skip data until first boundary - FirstBoundary, - /// Reading boundary - Boundary, - /// Reading Headers, - Headers, -} - -struct InnerMultipart { - payload: PayloadRef, - boundary: String, - state: InnerState, - item: InnerMultipartItem, -} - -impl Multipart { - /// Create multipart instance for boundary. - pub fn new(headers: &HeaderMap, stream: S) -> Multipart - where - S: Stream> + Unpin + 'static, - { - match Self::boundary(headers) { - Ok(boundary) => Multipart { - error: None, - safety: Safety::new(), - inner: Some(Rc::new(RefCell::new(InnerMultipart { - boundary, - payload: PayloadRef::new(PayloadBuffer::new(Box::new(stream))), - state: InnerState::FirstBoundary, - item: InnerMultipartItem::None, - }))), - }, - Err(err) => Multipart { - error: Some(err), - safety: Safety::new(), - inner: None, - }, - } - } - - /// Extract boundary info from headers. - fn boundary(headers: &HeaderMap) -> Result { - if let Some(content_type) = headers.get(&header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if let Ok(ct) = content_type.parse::() { - if let Some(boundary) = ct.get_param(mime::BOUNDARY) { - Ok(boundary.as_str().to_owned()) - } else { - Err(MultipartError::Boundary) - } - } else { - Err(MultipartError::ParseContentType) - } - } else { - Err(MultipartError::ParseContentType) - } - } else { - Err(MultipartError::NoContentType) - } - } -} - -impl Stream for Multipart { - type Item = Result; - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context, - ) -> Poll> { - if let Some(err) = self.error.take() { - Poll::Ready(Some(Err(err))) - } else if self.safety.current() { - let this = self.get_mut(); - let mut inner = this.inner.as_mut().unwrap().borrow_mut(); - if let Some(mut payload) = inner.payload.get_mut(&this.safety) { - payload.poll_stream(cx)?; - } - inner.poll(&this.safety, cx) - } else if !self.safety.is_clean() { - Poll::Ready(Some(Err(MultipartError::NotConsumed))) - } else { - Poll::Pending - } - } -} - -impl InnerMultipart { - fn read_headers( - payload: &mut PayloadBuffer, - ) -> Result, MultipartError> { - match payload.read_until(b"\r\n\r\n")? { - None => { - if payload.eof { - Err(MultipartError::Incomplete) - } else { - Ok(None) - } - } - Some(bytes) => { - let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS]; - match httparse::parse_headers(&bytes, &mut hdrs) { - Ok(httparse::Status::Complete((_, hdrs))) => { - // convert headers - let mut headers = HeaderMap::with_capacity(hdrs.len()); - for h in hdrs { - if let Ok(name) = HeaderName::try_from(h.name) { - if let Ok(value) = HeaderValue::try_from(h.value) { - headers.append(name, value); - } else { - return Err(ParseError::Header.into()); - } - } else { - return Err(ParseError::Header.into()); - } - } - Ok(Some(headers)) - } - Ok(httparse::Status::Partial) => Err(ParseError::Header.into()), - Err(err) => Err(ParseError::from(err).into()), - } - } - } - } - - fn read_boundary( - payload: &mut PayloadBuffer, - boundary: &str, - ) -> Result, MultipartError> { - // TODO: need to read epilogue - match payload.readline_or_eof()? { - None => { - if payload.eof { - Ok(Some(true)) - } else { - Ok(None) - } - } - Some(chunk) => { - if chunk.len() < boundary.len() + 4 - || &chunk[..2] != b"--" - || &chunk[2..boundary.len() + 2] != boundary.as_bytes() - { - Err(MultipartError::Boundary) - } else if &chunk[boundary.len() + 2..] == b"\r\n" { - Ok(Some(false)) - } else if &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" - && (chunk.len() == boundary.len() + 4 - || &chunk[boundary.len() + 4..] == b"\r\n") - { - Ok(Some(true)) - } else { - Err(MultipartError::Boundary) - } - } - } - } - - fn skip_until_boundary( - payload: &mut PayloadBuffer, - boundary: &str, - ) -> Result, MultipartError> { - let mut eof = false; - loop { - match payload.readline()? { - Some(chunk) => { - if chunk.is_empty() { - return Err(MultipartError::Boundary); - } - if chunk.len() < boundary.len() { - continue; - } - if &chunk[..2] == b"--" - && &chunk[2..chunk.len() - 2] == boundary.as_bytes() - { - break; - } else { - if chunk.len() < boundary.len() + 2 { - continue; - } - let b: &[u8] = boundary.as_ref(); - if &chunk[..boundary.len()] == b - && &chunk[boundary.len()..boundary.len() + 2] == b"--" - { - eof = true; - break; - } - } - } - None => { - return if payload.eof { - Err(MultipartError::Incomplete) - } else { - Ok(None) - }; - } - } - } - Ok(Some(eof)) - } - - fn poll( - &mut self, - safety: &Safety, - cx: &mut Context, - ) -> Poll>> { - if self.state == InnerState::Eof { - Poll::Ready(None) - } else { - // release field - loop { - // Nested multipart streams of fields has to be consumed - // before switching to next - if safety.current() { - let stop = match self.item { - InnerMultipartItem::Field(ref mut field) => { - match field.borrow_mut().poll(safety) { - Poll::Pending => return Poll::Pending, - Poll::Ready(Some(Ok(_))) => continue, - Poll::Ready(Some(Err(e))) => { - return Poll::Ready(Some(Err(e))) - } - Poll::Ready(None) => true, - } - } - InnerMultipartItem::None => false, - }; - if stop { - self.item = InnerMultipartItem::None; - } - if let InnerMultipartItem::None = self.item { - break; - } - } - } - - let headers = if let Some(mut payload) = self.payload.get_mut(safety) { - match self.state { - // read until first boundary - InnerState::FirstBoundary => { - match InnerMultipart::skip_until_boundary( - &mut *payload, - &self.boundary, - )? { - Some(eof) => { - if eof { - self.state = InnerState::Eof; - return Poll::Ready(None); - } else { - self.state = InnerState::Headers; - } - } - None => return Poll::Pending, - } - } - // read boundary - InnerState::Boundary => { - match InnerMultipart::read_boundary( - &mut *payload, - &self.boundary, - )? { - None => return Poll::Pending, - Some(eof) => { - if eof { - self.state = InnerState::Eof; - return Poll::Ready(None); - } else { - self.state = InnerState::Headers; - } - } - } - } - _ => (), - } - - // read field headers for next field - if self.state == InnerState::Headers { - if let Some(headers) = InnerMultipart::read_headers(&mut *payload)? { - self.state = InnerState::Boundary; - headers - } else { - return Poll::Pending; - } - } else { - unreachable!() - } - } else { - log::debug!("NotReady: field is in flight"); - return Poll::Pending; - }; - - // content type - let mut mt = mime::APPLICATION_OCTET_STREAM; - if let Some(content_type) = headers.get(&header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - if let Ok(ct) = content_type.parse::() { - mt = ct; - } - } - } - - self.state = InnerState::Boundary; - - // nested multipart stream - if mt.type_() == mime::MULTIPART { - Poll::Ready(Some(Err(MultipartError::Nested))) - } else { - let field = Rc::new(RefCell::new(InnerField::new( - self.payload.clone(), - self.boundary.clone(), - &headers, - )?)); - self.item = InnerMultipartItem::Field(Rc::clone(&field)); - - Poll::Ready(Some(Ok(Field::new(safety.clone(cx), headers, mt, field)))) - } - } - } -} - -impl Drop for InnerMultipart { - fn drop(&mut self) { - // InnerMultipartItem::Field has to be dropped first because of Safety. - self.item = InnerMultipartItem::None; - } -} - -/// A single field in a multipart stream -pub struct Field { - ct: mime::Mime, - headers: HeaderMap, - inner: Rc>, - safety: Safety, -} - -impl Field { - fn new( - safety: Safety, - headers: HeaderMap, - ct: mime::Mime, - inner: Rc>, - ) -> Self { - Field { - ct, - headers, - inner, - safety, - } - } - - /// Get a map of headers - pub fn headers(&self) -> &HeaderMap { - &self.headers - } - - /// Get the content type of the field - pub fn content_type(&self) -> &mime::Mime { - &self.ct - } - - /// Get the content disposition of the field, if it exists - pub fn content_disposition(&self) -> Option { - // RFC 7578: 'Each part MUST contain a Content-Disposition header field - // where the disposition type is "form-data".' - if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION) - { - ContentDisposition::from_raw(content_disposition).ok() - } else { - None - } - } -} - -impl Stream for Field { - type Item = Result; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - if self.safety.current() { - let mut inner = self.inner.borrow_mut(); - if let Some(mut payload) = - inner.payload.as_ref().unwrap().get_mut(&self.safety) - { - payload.poll_stream(cx)?; - } - inner.poll(&self.safety) - } else if !self.safety.is_clean() { - Poll::Ready(Some(Err(MultipartError::NotConsumed))) - } else { - Poll::Pending - } - } -} - -impl fmt::Debug for Field { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\nField: {}", self.ct)?; - writeln!(f, " boundary: {}", self.inner.borrow().boundary)?; - writeln!(f, " headers:")?; - for (key, val) in self.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -struct InnerField { - payload: Option, - boundary: String, - eof: bool, - length: Option, -} - -impl InnerField { - fn new( - payload: PayloadRef, - boundary: String, - headers: &HeaderMap, - ) -> Result { - let len = if let Some(len) = headers.get(&header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - Some(len) - } else { - return Err(PayloadError::Incomplete(None)); - } - } else { - return Err(PayloadError::Incomplete(None)); - } - } else { - None - }; - - Ok(InnerField { - boundary, - payload: Some(payload), - eof: false, - length: len, - }) - } - - /// Reads body part content chunk of the specified size. - /// The body part must has `Content-Length` header with proper value. - fn read_len( - payload: &mut PayloadBuffer, - size: &mut u64, - ) -> Poll>> { - if *size == 0 { - Poll::Ready(None) - } else { - match payload.read_max(*size)? { - Some(mut chunk) => { - let len = cmp::min(chunk.len() as u64, *size); - *size -= len; - let ch = chunk.split_to(len as usize); - if !chunk.is_empty() { - payload.unprocessed(chunk); - } - Poll::Ready(Some(Ok(ch))) - } - None => { - if payload.eof && (*size != 0) { - Poll::Ready(Some(Err(MultipartError::Incomplete))) - } else { - Poll::Pending - } - } - } - } - } - - /// Reads content chunk of body part with unknown length. - /// The `Content-Length` header for body part is not necessary. - fn read_stream( - payload: &mut PayloadBuffer, - boundary: &str, - ) -> Poll>> { - let mut pos = 0; - - let len = payload.buf.len(); - if len == 0 { - return if payload.eof { - Poll::Ready(Some(Err(MultipartError::Incomplete))) - } else { - Poll::Pending - }; - } - - // check boundary - if len > 4 && payload.buf[0] == b'\r' { - let b_len = if &payload.buf[..2] == b"\r\n" && &payload.buf[2..4] == b"--" { - Some(4) - } else if &payload.buf[1..3] == b"--" { - Some(3) - } else { - None - }; - - if let Some(b_len) = b_len { - let b_size = boundary.len() + b_len; - if len < b_size { - return Poll::Pending; - } else if &payload.buf[b_len..b_size] == boundary.as_bytes() { - // found boundary - return Poll::Ready(None); - } - } - } - - loop { - return if let Some(idx) = twoway::find_bytes(&payload.buf[pos..], b"\r") { - let cur = pos + idx; - - // check if we have enough data for boundary detection - if cur + 4 > len { - if cur > 0 { - Poll::Ready(Some(Ok(payload.buf.split_to(cur).freeze()))) - } else { - Poll::Pending - } - } else { - // check boundary - if (&payload.buf[cur..cur + 2] == b"\r\n" - && &payload.buf[cur + 2..cur + 4] == b"--") - || (&payload.buf[cur..=cur] == b"\r" - && &payload.buf[cur + 1..cur + 3] == b"--") - { - if cur != 0 { - // return buffer - Poll::Ready(Some(Ok(payload.buf.split_to(cur).freeze()))) - } else { - pos = cur + 1; - continue; - } - } else { - // not boundary - pos = cur + 1; - continue; - } - } - } else { - Poll::Ready(Some(Ok(payload.buf.split().freeze()))) - }; - } - } - - fn poll(&mut self, s: &Safety) -> Poll>> { - if self.payload.is_none() { - return Poll::Ready(None); - } - - let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) - { - if !self.eof { - let res = if let Some(ref mut len) = self.length { - InnerField::read_len(&mut *payload, len) - } else { - InnerField::read_stream(&mut *payload, &self.boundary) - }; - - match res { - Poll::Pending => return Poll::Pending, - Poll::Ready(Some(Ok(bytes))) => return Poll::Ready(Some(Ok(bytes))), - Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))), - Poll::Ready(None) => self.eof = true, - } - } - - match payload.readline() { - Ok(None) => Poll::Pending, - Ok(Some(line)) => { - if line.as_ref() != b"\r\n" { - log::warn!("multipart field did not read all the data or it is malformed"); - } - Poll::Ready(None) - } - Err(e) => Poll::Ready(Some(Err(e))), - } - } else { - Poll::Pending - }; - - if let Poll::Ready(None) = result { - self.payload.take(); - } - result - } -} - -struct PayloadRef { - payload: Rc>, -} - -impl PayloadRef { - fn new(payload: PayloadBuffer) -> PayloadRef { - PayloadRef { - payload: Rc::new(payload.into()), - } - } - - fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option> - where - 'a: 'b, - { - if s.current() { - Some(self.payload.borrow_mut()) - } else { - None - } - } -} - -impl Clone for PayloadRef { - fn clone(&self) -> PayloadRef { - PayloadRef { - payload: Rc::clone(&self.payload), - } - } -} - -/// Counter. It tracks of number of clones of payloads and give access to -/// payload only to top most task panics if Safety get destroyed and it not top -/// most task. -#[derive(Debug)] -struct Safety { - task: LocalWaker, - level: usize, - payload: Rc>, - clean: Rc>, -} - -impl Safety { - fn new() -> Safety { - let payload = Rc::new(PhantomData); - Safety { - task: LocalWaker::new(), - level: Rc::strong_count(&payload), - clean: Rc::new(Cell::new(true)), - payload, - } - } - - fn current(&self) -> bool { - Rc::strong_count(&self.payload) == self.level && self.clean.get() - } - - fn is_clean(&self) -> bool { - self.clean.get() - } - - fn clone(&self, cx: &mut Context) -> Safety { - let payload = Rc::clone(&self.payload); - let s = Safety { - task: LocalWaker::new(), - level: Rc::strong_count(&payload), - clean: self.clean.clone(), - payload, - }; - s.task.register(cx.waker()); - s - } -} - -impl Drop for Safety { - fn drop(&mut self) { - // parent task is dead - if Rc::strong_count(&self.payload) != self.level { - self.clean.set(true); - } - if let Some(task) = self.task.take() { - task.wake() - } - } -} - -/// Payload buffer -struct PayloadBuffer { - eof: bool, - buf: BytesMut, - stream: LocalBoxStream<'static, Result>, -} - -impl PayloadBuffer { - /// Create new `PayloadBuffer` instance - fn new(stream: S) -> Self - where - S: Stream> + 'static, - { - PayloadBuffer { - eof: false, - buf: BytesMut::new(), - stream: stream.boxed_local(), - } - } - - fn poll_stream(&mut self, cx: &mut Context) -> Result<(), PayloadError> { - loop { - match Pin::new(&mut self.stream).poll_next(cx) { - Poll::Ready(Some(Ok(data))) => self.buf.extend_from_slice(&data), - Poll::Ready(Some(Err(e))) => return Err(e), - Poll::Ready(None) => { - self.eof = true; - return Ok(()); - } - Poll::Pending => return Ok(()), - } - } - } - - /// Read exact number of bytes - #[cfg(test)] - fn read_exact(&mut self, size: usize) -> Option { - if size <= self.buf.len() { - Some(self.buf.split_to(size).freeze()) - } else { - None - } - } - - fn read_max(&mut self, size: u64) -> Result, MultipartError> { - if !self.buf.is_empty() { - let size = std::cmp::min(self.buf.len() as u64, size) as usize; - Ok(Some(self.buf.split_to(size).freeze())) - } else if self.eof { - Err(MultipartError::Incomplete) - } else { - Ok(None) - } - } - - /// Read until specified ending - pub fn read_until(&mut self, line: &[u8]) -> Result, MultipartError> { - let res = twoway::find_bytes(&self.buf, line) - .map(|idx| self.buf.split_to(idx + line.len()).freeze()); - - if res.is_none() && self.eof { - Err(MultipartError::Incomplete) - } else { - Ok(res) - } - } - - /// Read bytes until new line delimiter - pub fn readline(&mut self) -> Result, MultipartError> { - self.read_until(b"\n") - } - - /// Read bytes until new line delimiter or eof - pub fn readline_or_eof(&mut self) -> Result, MultipartError> { - match self.readline() { - Err(MultipartError::Incomplete) if self.eof => { - Ok(Some(self.buf.split().freeze())) - } - line => line, - } - } - - /// Put unprocessed data back to the buffer - pub fn unprocessed(&mut self, data: Bytes) { - let buf = BytesMut::from(data.as_ref()); - let buf = std::mem::replace(&mut self.buf, buf); - self.buf.extend_from_slice(&buf); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use actix_http::h1::Payload; - use actix_utils::mpsc; - use actix_web::http::header::{DispositionParam, DispositionType}; - use bytes::Bytes; - use futures::future::lazy; - - #[actix_rt::test] - async fn test_boundary() { - let headers = HeaderMap::new(); - match Multipart::boundary(&headers) { - Err(MultipartError::NoContentType) => (), - _ => unreachable!("should not happen"), - } - - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("test"), - ); - - match Multipart::boundary(&headers) { - Err(MultipartError::ParseContentType) => (), - _ => unreachable!("should not happen"), - } - - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("multipart/mixed"), - ); - match Multipart::boundary(&headers) { - Err(MultipartError::Boundary) => (), - _ => unreachable!("should not happen"), - } - - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static( - "multipart/mixed; boundary=\"5c02368e880e436dab70ed54e1c58209\"", - ), - ); - - assert_eq!( - Multipart::boundary(&headers).unwrap(), - "5c02368e880e436dab70ed54e1c58209" - ); - } - - fn create_stream() -> ( - mpsc::Sender>, - impl Stream>, - ) { - let (tx, rx) = mpsc::channel(); - - (tx, rx.map(|res| res.map_err(|_| panic!()))) - } - // Stream that returns from a Bytes, one char at a time and Pending every other poll() - struct SlowStream { - bytes: Bytes, - pos: usize, - ready: bool, - } - - impl SlowStream { - fn new(bytes: Bytes) -> SlowStream { - return SlowStream { - bytes: bytes, - pos: 0, - ready: false, - }; - } - } - - impl Stream for SlowStream { - type Item = Result; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context, - ) -> Poll> { - let this = self.get_mut(); - if !this.ready { - this.ready = true; - cx.waker().wake_by_ref(); - return Poll::Pending; - } - if this.pos == this.bytes.len() { - return Poll::Ready(None); - } - let res = Poll::Ready(Some(Ok(this.bytes.slice(this.pos..(this.pos + 1))))); - this.pos += 1; - this.ready = false; - res - } - } - - fn create_simple_request_with_header() -> (Bytes, HeaderMap) { - let bytes = Bytes::from( - "testasdadsad\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\ - Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ - test\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ - Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ - data\r\n\ - --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n", - ); - let mut headers = HeaderMap::new(); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static( - "multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"", - ), - ); - (bytes, headers) - } - - #[actix_rt::test] - async fn test_multipart_no_end_crlf() { - let (sender, payload) = create_stream(); - let (mut bytes, headers) = create_simple_request_with_header(); - let bytes_stripped = bytes.split_to(bytes.len()); // strip crlf - - sender.send(Ok(bytes_stripped)).unwrap(); - drop(sender); // eof - - let mut multipart = Multipart::new(&headers, payload); - - match multipart.next().await.unwrap() { - Ok(_) => (), - _ => unreachable!(), - } - - match multipart.next().await.unwrap() { - Ok(_) => (), - _ => unreachable!(), - } - - match multipart.next().await { - None => (), - _ => unreachable!(), - } - } - - #[actix_rt::test] - async fn test_multipart() { - let (sender, payload) = create_stream(); - let (bytes, headers) = create_simple_request_with_header(); - - sender.send(Ok(bytes)).unwrap(); - - let mut multipart = Multipart::new(&headers, payload); - match multipart.next().await { - Some(Ok(mut field)) => { - let cd = field.content_disposition().unwrap(); - assert_eq!(cd.disposition, DispositionType::FormData); - assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); - - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - match field.next().await.unwrap() { - Ok(chunk) => assert_eq!(chunk, "test"), - _ => unreachable!(), - } - match field.next().await { - None => (), - _ => unreachable!(), - } - } - _ => unreachable!(), - } - - match multipart.next().await.unwrap() { - Ok(mut field) => { - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - match field.next().await { - Some(Ok(chunk)) => assert_eq!(chunk, "data"), - _ => unreachable!(), - } - match field.next().await { - None => (), - _ => unreachable!(), - } - } - _ => unreachable!(), - } - - match multipart.next().await { - None => (), - _ => unreachable!(), - } - } - - // Loops, collecting all bytes until end-of-field - async fn get_whole_field(field: &mut Field) -> BytesMut { - let mut b = BytesMut::new(); - loop { - match field.next().await { - Some(Ok(chunk)) => b.extend_from_slice(&chunk), - None => return b, - _ => unreachable!(), - } - } - } - - #[actix_rt::test] - async fn test_stream() { - let (bytes, headers) = create_simple_request_with_header(); - let payload = SlowStream::new(bytes); - - let mut multipart = Multipart::new(&headers, payload); - match multipart.next().await.unwrap() { - Ok(mut field) => { - let cd = field.content_disposition().unwrap(); - assert_eq!(cd.disposition, DispositionType::FormData); - assert_eq!(cd.parameters[0], DispositionParam::Name("file".into())); - - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - assert_eq!(get_whole_field(&mut field).await, "test"); - } - _ => unreachable!(), - } - - match multipart.next().await { - Some(Ok(mut field)) => { - assert_eq!(field.content_type().type_(), mime::TEXT); - assert_eq!(field.content_type().subtype(), mime::PLAIN); - - assert_eq!(get_whole_field(&mut field).await, "data"); - } - _ => unreachable!(), - } - - match multipart.next().await { - None => (), - _ => unreachable!(), - } - } - - #[actix_rt::test] - async fn test_basic() { - let (_, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(payload.buf.len(), 0); - lazy(|cx| payload.poll_stream(cx)).await.unwrap(); - assert_eq!(None, payload.read_max(1).unwrap()); - } - - #[actix_rt::test] - async fn test_eof() { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(None, payload.read_max(4).unwrap()); - sender.feed_data(Bytes::from("data")); - sender.feed_eof(); - lazy(|cx| payload.poll_stream(cx)).await.unwrap(); - - assert_eq!(Some(Bytes::from("data")), payload.read_max(4).unwrap()); - assert_eq!(payload.buf.len(), 0); - assert!(payload.read_max(1).is_err()); - assert!(payload.eof); - } - - #[actix_rt::test] - async fn test_err() { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - assert_eq!(None, payload.read_max(1).unwrap()); - sender.set_error(PayloadError::Incomplete(None)); - lazy(|cx| payload.poll_stream(cx)).await.err().unwrap(); - } - - #[actix_rt::test] - async fn test_readmax() { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - lazy(|cx| payload.poll_stream(cx)).await.unwrap(); - assert_eq!(payload.buf.len(), 10); - - assert_eq!(Some(Bytes::from("line1")), payload.read_max(5).unwrap()); - assert_eq!(payload.buf.len(), 5); - - assert_eq!(Some(Bytes::from("line2")), payload.read_max(5).unwrap()); - assert_eq!(payload.buf.len(), 0); - } - - #[actix_rt::test] - async fn test_readexactly() { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(None, payload.read_exact(2)); - - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - lazy(|cx| payload.poll_stream(cx)).await.unwrap(); - - assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2)); - assert_eq!(payload.buf.len(), 8); - - assert_eq!(Some(Bytes::from_static(b"ne1l")), payload.read_exact(4)); - assert_eq!(payload.buf.len(), 4); - } - - #[actix_rt::test] - async fn test_readuntil() { - let (mut sender, payload) = Payload::create(false); - let mut payload = PayloadBuffer::new(payload); - - assert_eq!(None, payload.read_until(b"ne").unwrap()); - - sender.feed_data(Bytes::from("line1")); - sender.feed_data(Bytes::from("line2")); - lazy(|cx| payload.poll_stream(cx)).await.unwrap(); - - assert_eq!( - Some(Bytes::from("line")), - payload.read_until(b"ne").unwrap() - ); - assert_eq!(payload.buf.len(), 6); - - assert_eq!( - Some(Bytes::from("1line2")), - payload.read_until(b"2").unwrap() - ); - assert_eq!(payload.buf.len(), 0); - } -} diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md deleted file mode 100644 index 66ff7ed6c..000000000 --- a/actix-web-actors/CHANGES.md +++ /dev/null @@ -1,44 +0,0 @@ -# Changes - -## [2.0.0] - 2019-12-20 - -* Release - -## [2.0.0-alpha.1] - 2019-12-15 - -* Migrate to actix-web 2.0.0 - -## [1.0.4] - 2019-12-07 - -* Allow comma-separated websocket subprotocols without spaces (#1172) - -## [1.0.3] - 2019-11-14 - -* Update actix-web and actix-http dependencies - -## [1.0.2] - 2019-07-20 - -* Add `ws::start_with_addr()`, returning the address of the created actor, along - with the `HttpResponse`. - -* Add support for specifying protocols on websocket handshake #835 - -## [1.0.1] - 2019-06-28 - -* Allow to use custom ws codec with `WebsocketContext` #925 - -## [1.0.0] - 2019-05-29 - -* Update actix-http and actix-web - -## [0.1.0-alpha.3] - 2019-04-02 - -* Update actix-http and actix-web - -## [0.1.0-alpha.2] - 2019-03-29 - -* Update actix-http and actix-web - -## [0.1.0-alpha.1] - 2019-03-28 - -* Initial impl diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml deleted file mode 100644 index 6f573e442..000000000 --- a/actix-web-actors/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "actix-web-actors" -version = "2.0.0" -authors = ["Nikolay Kim "] -description = "Actix actors support for actix web framework." -readme = "README.md" -keywords = ["actix", "http", "web", "framework", "async"] -homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/actix-web-actors/" -license = "MIT/Apache-2.0" -edition = "2018" - -[lib] -name = "actix_web_actors" -path = "src/lib.rs" - -[dependencies] -actix = "0.9.0" -actix-web = "2.0.0-rc" -actix-http = "1.0.1" -actix-codec = "0.2.0" -bytes = "0.5.2" -futures = "0.3.1" -pin-project = "0.4.6" - -[dev-dependencies] -actix-rt = "1.0.0" -env_logger = "0.6" diff --git a/actix-web-actors/LICENSE-APACHE b/actix-web-actors/LICENSE-APACHE deleted file mode 120000 index 965b606f3..000000000 --- a/actix-web-actors/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/actix-web-actors/LICENSE-MIT b/actix-web-actors/LICENSE-MIT deleted file mode 120000 index 76219eb72..000000000 --- a/actix-web-actors/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md deleted file mode 100644 index 6ff7ac67c..000000000 --- a/actix-web-actors/README.md +++ /dev/null @@ -1,8 +0,0 @@ -Actix actors support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web-actors)](https://crates.io/crates/actix-web-actors) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -## Documentation & community resources - -* [API Documentation](https://docs.rs/actix-web-actors/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-web-actors](https://crates.io/crates/actix-web-actors) -* Minimum supported Rust version: 1.33 or later diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs deleted file mode 100644 index 6a403de12..000000000 --- a/actix-web-actors/src/context.rs +++ /dev/null @@ -1,248 +0,0 @@ -use std::collections::VecDeque; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope, -}; -use actix::fut::ActorFuture; -use actix::{ - Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, -}; -use actix_web::error::Error; -use bytes::Bytes; -use futures::channel::oneshot::Sender; -use futures::{Future, Stream}; - -/// Execution context for http actors -pub struct HttpContext -where - A: Actor>, -{ - inner: ContextParts, - stream: VecDeque>, -} - -impl ActorContext for HttpContext -where - A: Actor, -{ - fn stop(&mut self) { - self.inner.stop(); - } - fn terminate(&mut self) { - self.inner.terminate() - } - fn state(&self) -> ActorState { - self.inner.state() - } -} - -impl AsyncContext for HttpContext -where - A: Actor, -{ - #[inline] - fn spawn(&mut self, fut: F) -> SpawnHandle - where - F: ActorFuture + 'static, - { - self.inner.spawn(fut) - } - - #[inline] - fn wait(&mut self, fut: F) - where - F: ActorFuture + 'static, - { - self.inner.wait(fut) - } - - #[doc(hidden)] - #[inline] - fn waiting(&self) -> bool { - self.inner.waiting() - || self.inner.state() == ActorState::Stopping - || self.inner.state() == ActorState::Stopped - } - - #[inline] - fn cancel_future(&mut self, handle: SpawnHandle) -> bool { - self.inner.cancel_future(handle) - } - - #[inline] - fn address(&self) -> Addr { - self.inner.address() - } -} - -impl HttpContext -where - A: Actor, -{ - #[inline] - /// Create a new HTTP Context from a request and an actor - pub fn create(actor: A) -> impl Stream> { - let mb = Mailbox::default(); - let ctx = HttpContext { - inner: ContextParts::new(mb.sender_producer()), - stream: VecDeque::new(), - }; - HttpContextFut::new(ctx, actor, mb) - } - - /// Create a new HTTP Context - pub fn with_factory(f: F) -> impl Stream> - where - F: FnOnce(&mut Self) -> A + 'static, - { - let mb = Mailbox::default(); - let mut ctx = HttpContext { - inner: ContextParts::new(mb.sender_producer()), - stream: VecDeque::new(), - }; - - let act = f(&mut ctx); - HttpContextFut::new(ctx, act, mb) - } -} - -impl HttpContext -where - A: Actor, -{ - /// Write payload - #[inline] - pub fn write(&mut self, data: Bytes) { - self.stream.push_back(Some(data)); - } - - /// Indicate end of streaming payload. Also this method calls `Self::close`. - #[inline] - pub fn write_eof(&mut self) { - self.stream.push_back(None); - } - - /// Handle of the running future - /// - /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. - pub fn handle(&self) -> SpawnHandle { - self.inner.curr_handle() - } -} - -impl AsyncContextParts for HttpContext -where - A: Actor, -{ - fn parts(&mut self) -> &mut ContextParts { - &mut self.inner - } -} - -struct HttpContextFut -where - A: Actor>, -{ - fut: ContextFut>, -} - -impl HttpContextFut -where - A: Actor>, -{ - fn new(ctx: HttpContext, act: A, mailbox: Mailbox) -> Self { - let fut = ContextFut::new(ctx, act, mailbox); - HttpContextFut { fut } - } -} - -impl Stream for HttpContextFut -where - A: Actor>, -{ - type Item = Result; - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - if self.fut.alive() { - let _ = Pin::new(&mut self.fut).poll(cx); - } - - // frames - if let Some(data) = self.fut.ctx().stream.pop_front() { - Poll::Ready(data.map(|b| Ok(b))) - } else if self.fut.alive() { - Poll::Pending - } else { - Poll::Ready(None) - } - } -} - -impl ToEnvelope for HttpContext -where - A: Actor> + Handler, - M: Message + Send + 'static, - M::Result: Send, -{ - fn pack(msg: M, tx: Option>) -> Envelope { - Envelope::new(msg, tx) - } -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use actix::Actor; - use actix_web::http::StatusCode; - use actix_web::test::{call_service, init_service, read_body, TestRequest}; - use actix_web::{web, App, HttpResponse}; - use bytes::Bytes; - - use super::*; - - struct MyActor { - count: usize, - } - - impl Actor for MyActor { - type Context = HttpContext; - - fn started(&mut self, ctx: &mut Self::Context) { - ctx.run_later(Duration::from_millis(100), |slf, ctx| slf.write(ctx)); - } - } - - impl MyActor { - fn write(&mut self, ctx: &mut HttpContext) { - self.count += 1; - if self.count > 3 { - ctx.write_eof() - } else { - ctx.write(Bytes::from(format!("LINE-{}", self.count))); - ctx.run_later(Duration::from_millis(100), |slf, ctx| slf.write(ctx)); - } - } - } - - #[actix_rt::test] - async fn test_default_resource() { - let mut srv = - init_service(App::new().service(web::resource("/test").to(|| { - HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 })) - }))) - .await; - - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - - let body = read_body(resp).await; - assert_eq!(body, Bytes::from_static(b"LINE-1LINE-2LINE-3")); - } -} diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs deleted file mode 100644 index 6360917cd..000000000 --- a/actix-web-actors/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![allow(clippy::borrow_interior_mutable_const)] -//! Actix actors integration for Actix web framework -mod context; -pub mod ws; - -pub use self::context::HttpContext; diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs deleted file mode 100644 index b28aeade4..000000000 --- a/actix-web-actors/src/ws.rs +++ /dev/null @@ -1,794 +0,0 @@ -//! Websocket integration -use std::collections::VecDeque; -use std::io; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, - ToEnvelope, -}; -use actix::fut::ActorFuture; -use actix::{ - Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, - Message as ActixMessage, SpawnHandle, -}; -use actix_codec::{Decoder, Encoder}; -use actix_http::ws::{hash_key, Codec}; -pub use actix_http::ws::{ - CloseCode, CloseReason, Frame, HandshakeError, Message, ProtocolError, -}; -use actix_web::dev::HttpResponseBuilder; -use actix_web::error::{Error, PayloadError}; -use actix_web::http::{header, Method, StatusCode}; -use actix_web::{HttpRequest, HttpResponse}; -use bytes::{Bytes, BytesMut}; -use futures::channel::oneshot::Sender; -use futures::{Future, Stream}; - -/// Do websocket handshake and start ws actor. -pub fn start(actor: A, req: &HttpRequest, stream: T) -> Result -where - A: Actor> - + StreamHandler>, - T: Stream> + 'static, -{ - let mut res = handshake(req)?; - Ok(res.streaming(WebsocketContext::create(actor, stream))) -} - -/// Do websocket handshake and start ws actor. -/// -/// `req` is an HTTP Request that should be requesting a websocket protocol -/// change. `stream` should be a `Bytes` stream (such as -/// `actix_web::web::Payload`) that contains a stream of the body request. -/// -/// If there is a problem with the handshake, an error is returned. -/// -/// If successful, returns a pair where the first item is an address for the -/// created actor and the second item is the response that should be returned -/// from the websocket request. -pub fn start_with_addr( - actor: A, - req: &HttpRequest, - stream: T, -) -> Result<(Addr, HttpResponse), Error> -where - A: Actor> - + StreamHandler>, - T: Stream> + 'static, -{ - let mut res = handshake(req)?; - let (addr, out_stream) = WebsocketContext::create_with_addr(actor, stream); - Ok((addr, res.streaming(out_stream))) -} - -/// Do websocket handshake and start ws actor. -/// -/// `protocols` is a sequence of known protocols. -pub fn start_with_protocols( - actor: A, - protocols: &[&str], - req: &HttpRequest, - stream: T, -) -> Result -where - A: Actor> - + StreamHandler>, - T: Stream> + 'static, -{ - let mut res = handshake_with_protocols(req, protocols)?; - Ok(res.streaming(WebsocketContext::create(actor, stream))) -} - -/// Prepare `WebSocket` handshake response. -/// -/// This function returns handshake `HttpResponse`, ready to send to peer. -/// It does not perform any IO. -pub fn handshake(req: &HttpRequest) -> Result { - handshake_with_protocols(req, &[]) -} - -/// Prepare `WebSocket` handshake response. -/// -/// This function returns handshake `HttpResponse`, ready to send to peer. -/// It does not perform any IO. -/// -/// `protocols` is a sequence of known protocols. On successful handshake, -/// the returned response headers contain the first protocol in this list -/// which the server also knows. -pub fn handshake_with_protocols( - req: &HttpRequest, - protocols: &[&str], -) -> Result { - // WebSocket accepts only GET - if *req.method() != Method::GET { - return Err(HandshakeError::GetMethodRequired); - } - - // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = req.headers().get(&header::UPGRADE) { - if let Ok(s) = hdr.to_str() { - s.to_ascii_lowercase().contains("websocket") - } else { - false - } - } else { - false - }; - if !has_hdr { - return Err(HandshakeError::NoWebsocketUpgrade); - } - - // Upgrade connection - if !req.head().upgrade() { - return Err(HandshakeError::NoConnectionUpgrade); - } - - // check supported version - if !req.headers().contains_key(&header::SEC_WEBSOCKET_VERSION) { - return Err(HandshakeError::NoVersionHeader); - } - let supported_ver = { - if let Some(hdr) = req.headers().get(&header::SEC_WEBSOCKET_VERSION) { - hdr == "13" || hdr == "8" || hdr == "7" - } else { - false - } - }; - if !supported_ver { - return Err(HandshakeError::UnsupportedVersion); - } - - // check client handshake for validity - if !req.headers().contains_key(&header::SEC_WEBSOCKET_KEY) { - return Err(HandshakeError::BadWebsocketKey); - } - let key = { - let key = req.headers().get(&header::SEC_WEBSOCKET_KEY).unwrap(); - hash_key(key.as_ref()) - }; - - // check requested protocols - let protocol = - req.headers() - .get(&header::SEC_WEBSOCKET_PROTOCOL) - .and_then(|req_protocols| { - let req_protocols = req_protocols.to_str().ok()?; - req_protocols - .split(',') - .map(|req_p| req_p.trim()) - .find(|req_p| protocols.iter().any(|p| p == req_p)) - }); - - let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) - .upgrade("websocket") - .header(header::TRANSFER_ENCODING, "chunked") - .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) - .take(); - - if let Some(protocol) = protocol { - response.header(&header::SEC_WEBSOCKET_PROTOCOL, protocol); - } - - Ok(response) -} - -/// Execution context for `WebSockets` actors -pub struct WebsocketContext -where - A: Actor>, -{ - inner: ContextParts, - messages: VecDeque>, -} - -impl ActorContext for WebsocketContext -where - A: Actor, -{ - fn stop(&mut self) { - self.inner.stop(); - } - - fn terminate(&mut self) { - self.inner.terminate() - } - - fn state(&self) -> ActorState { - self.inner.state() - } -} - -impl AsyncContext for WebsocketContext -where - A: Actor, -{ - fn spawn(&mut self, fut: F) -> SpawnHandle - where - F: ActorFuture + 'static, - { - self.inner.spawn(fut) - } - - fn wait(&mut self, fut: F) - where - F: ActorFuture + 'static, - { - self.inner.wait(fut) - } - - #[doc(hidden)] - #[inline] - fn waiting(&self) -> bool { - self.inner.waiting() - || self.inner.state() == ActorState::Stopping - || self.inner.state() == ActorState::Stopped - } - - fn cancel_future(&mut self, handle: SpawnHandle) -> bool { - self.inner.cancel_future(handle) - } - - #[inline] - fn address(&self) -> Addr { - self.inner.address() - } -} - -impl WebsocketContext -where - A: Actor, -{ - #[inline] - /// Create a new Websocket context from a request and an actor - pub fn create(actor: A, stream: S) -> impl Stream> - where - A: StreamHandler>, - S: Stream> + 'static, - { - let (_, stream) = WebsocketContext::create_with_addr(actor, stream); - stream - } - - #[inline] - /// Create a new Websocket context from a request and an actor. - /// - /// Returns a pair, where the first item is an addr for the created actor, - /// and the second item is a stream intended to be set as part of the - /// response via `HttpResponseBuilder::streaming()`. - pub fn create_with_addr( - actor: A, - stream: S, - ) -> (Addr, impl Stream>) - where - A: StreamHandler>, - S: Stream> + 'static, - { - let mb = Mailbox::default(); - let mut ctx = WebsocketContext { - inner: ContextParts::new(mb.sender_producer()), - messages: VecDeque::new(), - }; - ctx.add_stream(WsStream::new(stream, Codec::new())); - - let addr = ctx.address(); - - (addr, WebsocketContextFut::new(ctx, actor, mb, Codec::new())) - } - - #[inline] - /// Create a new Websocket context from a request, an actor, and a codec - pub fn with_codec( - actor: A, - stream: S, - codec: Codec, - ) -> impl Stream> - where - A: StreamHandler>, - S: Stream> + 'static, - { - let mb = Mailbox::default(); - let mut ctx = WebsocketContext { - inner: ContextParts::new(mb.sender_producer()), - messages: VecDeque::new(), - }; - ctx.add_stream(WsStream::new(stream, codec)); - - WebsocketContextFut::new(ctx, actor, mb, codec) - } - - /// Create a new Websocket context - pub fn with_factory( - stream: S, - f: F, - ) -> impl Stream> - where - F: FnOnce(&mut Self) -> A + 'static, - A: StreamHandler>, - S: Stream> + 'static, - { - let mb = Mailbox::default(); - let mut ctx = WebsocketContext { - inner: ContextParts::new(mb.sender_producer()), - messages: VecDeque::new(), - }; - ctx.add_stream(WsStream::new(stream, Codec::new())); - - let act = f(&mut ctx); - - WebsocketContextFut::new(ctx, act, mb, Codec::new()) - } -} - -impl WebsocketContext -where - A: Actor, -{ - /// Write payload - /// - /// This is a low-level function that accepts framed messages that should - /// be created using `Frame::message()`. If you want to send text or binary - /// data you should prefer the `text()` or `binary()` convenience functions - /// that handle the framing for you. - #[inline] - pub fn write_raw(&mut self, msg: Message) { - self.messages.push_back(Some(msg)); - } - - /// Send text frame - #[inline] - pub fn text>(&mut self, text: T) { - self.write_raw(Message::Text(text.into())); - } - - /// Send binary frame - #[inline] - pub fn binary>(&mut self, data: B) { - self.write_raw(Message::Binary(data.into())); - } - - /// Send ping frame - #[inline] - pub fn ping(&mut self, message: &[u8]) { - self.write_raw(Message::Ping(Bytes::copy_from_slice(message))); - } - - /// Send pong frame - #[inline] - pub fn pong(&mut self, message: &[u8]) { - self.write_raw(Message::Pong(Bytes::copy_from_slice(message))); - } - - /// Send close frame - #[inline] - pub fn close(&mut self, reason: Option) { - self.write_raw(Message::Close(reason)); - } - - /// Handle of the running future - /// - /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. - pub fn handle(&self) -> SpawnHandle { - self.inner.curr_handle() - } - - /// Set mailbox capacity - /// - /// By default mailbox capacity is 16 messages. - pub fn set_mailbox_capacity(&mut self, cap: usize) { - self.inner.set_mailbox_capacity(cap) - } -} - -impl AsyncContextParts for WebsocketContext -where - A: Actor, -{ - fn parts(&mut self) -> &mut ContextParts { - &mut self.inner - } -} - -struct WebsocketContextFut -where - A: Actor>, -{ - fut: ContextFut>, - encoder: Codec, - buf: BytesMut, - closed: bool, -} - -impl WebsocketContextFut -where - A: Actor>, -{ - fn new(ctx: WebsocketContext, act: A, mailbox: Mailbox, codec: Codec) -> Self { - let fut = ContextFut::new(ctx, act, mailbox); - WebsocketContextFut { - fut, - encoder: codec, - buf: BytesMut::new(), - closed: false, - } - } -} - -impl Stream for WebsocketContextFut -where - A: Actor>, -{ - type Item = Result; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - let this = self.get_mut(); - - if this.fut.alive() { - let _ = Pin::new(&mut this.fut).poll(cx); - } - - // encode messages - while let Some(item) = this.fut.ctx().messages.pop_front() { - if let Some(msg) = item { - this.encoder.encode(msg, &mut this.buf)?; - } else { - this.closed = true; - break; - } - } - - if !this.buf.is_empty() { - Poll::Ready(Some(Ok(this.buf.split().freeze()))) - } else if this.fut.alive() && !this.closed { - Poll::Pending - } else { - Poll::Ready(None) - } - } -} - -impl ToEnvelope for WebsocketContext -where - A: Actor> + Handler, - M: ActixMessage + Send + 'static, - M::Result: Send, -{ - fn pack(msg: M, tx: Option>) -> Envelope { - Envelope::new(msg, tx) - } -} - -#[pin_project::pin_project] -struct WsStream { - #[pin] - stream: S, - decoder: Codec, - buf: BytesMut, - closed: bool, -} - -impl WsStream -where - S: Stream>, -{ - fn new(stream: S, codec: Codec) -> Self { - Self { - stream, - decoder: codec, - buf: BytesMut::new(), - closed: false, - } - } -} - -impl Stream for WsStream -where - S: Stream>, -{ - type Item = Result; - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - let mut this = self.as_mut().project(); - - if !*this.closed { - loop { - this = self.as_mut().project(); - match Pin::new(&mut this.stream).poll_next(cx) { - Poll::Ready(Some(Ok(chunk))) => { - this.buf.extend_from_slice(&chunk[..]); - } - Poll::Ready(None) => { - *this.closed = true; - break; - } - Poll::Pending => break, - Poll::Ready(Some(Err(e))) => { - return Poll::Ready(Some(Err(ProtocolError::Io( - io::Error::new(io::ErrorKind::Other, format!("{}", e)), - )))); - } - } - } - } - - match this.decoder.decode(this.buf)? { - None => { - if *this.closed { - Poll::Ready(None) - } else { - Poll::Pending - } - } - Some(frm) => { - let msg = match frm { - Frame::Text(data) => Message::Text( - std::str::from_utf8(&data) - .map_err(|e| { - ProtocolError::Io(io::Error::new( - io::ErrorKind::Other, - format!("{}", e), - )) - })? - .to_string(), - ), - Frame::Binary(data) => Message::Binary(data), - Frame::Ping(s) => Message::Ping(s), - Frame::Pong(s) => Message::Pong(s), - Frame::Close(reason) => Message::Close(reason), - Frame::Continuation(item) => Message::Continuation(item), - }; - Poll::Ready(Some(Ok(msg))) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use actix_web::http::{header, Method}; - use actix_web::test::TestRequest; - - #[test] - fn test_handshake() { - let req = TestRequest::default() - .method(Method::POST) - .to_http_request(); - assert_eq!( - HandshakeError::GetMethodRequired, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default().to_http_request(); - assert_eq!( - HandshakeError::NoWebsocketUpgrade, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default() - .header(header::UPGRADE, header::HeaderValue::from_static("test")) - .to_http_request(); - assert_eq!( - HandshakeError::NoWebsocketUpgrade, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .to_http_request(); - assert_eq!( - HandshakeError::NoConnectionUpgrade, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ) - .to_http_request(); - assert_eq!( - HandshakeError::NoVersionHeader, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ) - .header( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("5"), - ) - .to_http_request(); - assert_eq!( - HandshakeError::UnsupportedVersion, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ) - .header( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13"), - ) - .to_http_request(); - assert_eq!( - HandshakeError::BadWebsocketKey, - handshake(&req).err().unwrap() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ) - .header( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13"), - ) - .header( - header::SEC_WEBSOCKET_KEY, - header::HeaderValue::from_static("13"), - ) - .to_http_request(); - - assert_eq!( - StatusCode::SWITCHING_PROTOCOLS, - handshake(&req).unwrap().finish().status() - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ) - .header( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13"), - ) - .header( - header::SEC_WEBSOCKET_KEY, - header::HeaderValue::from_static("13"), - ) - .header( - header::SEC_WEBSOCKET_PROTOCOL, - header::HeaderValue::from_static("graphql"), - ) - .to_http_request(); - - let protocols = ["graphql"]; - - assert_eq!( - StatusCode::SWITCHING_PROTOCOLS, - handshake_with_protocols(&req, &protocols) - .unwrap() - .finish() - .status() - ); - assert_eq!( - Some(&header::HeaderValue::from_static("graphql")), - handshake_with_protocols(&req, &protocols) - .unwrap() - .finish() - .headers() - .get(&header::SEC_WEBSOCKET_PROTOCOL) - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ) - .header( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13"), - ) - .header( - header::SEC_WEBSOCKET_KEY, - header::HeaderValue::from_static("13"), - ) - .header( - header::SEC_WEBSOCKET_PROTOCOL, - header::HeaderValue::from_static("p1, p2, p3"), - ) - .to_http_request(); - - let protocols = vec!["p3", "p2"]; - - assert_eq!( - StatusCode::SWITCHING_PROTOCOLS, - handshake_with_protocols(&req, &protocols) - .unwrap() - .finish() - .status() - ); - assert_eq!( - Some(&header::HeaderValue::from_static("p2")), - handshake_with_protocols(&req, &protocols) - .unwrap() - .finish() - .headers() - .get(&header::SEC_WEBSOCKET_PROTOCOL) - ); - - let req = TestRequest::default() - .header( - header::UPGRADE, - header::HeaderValue::from_static("websocket"), - ) - .header( - header::CONNECTION, - header::HeaderValue::from_static("upgrade"), - ) - .header( - header::SEC_WEBSOCKET_VERSION, - header::HeaderValue::from_static("13"), - ) - .header( - header::SEC_WEBSOCKET_KEY, - header::HeaderValue::from_static("13"), - ) - .header( - header::SEC_WEBSOCKET_PROTOCOL, - header::HeaderValue::from_static("p1,p2,p3"), - ) - .to_http_request(); - - let protocols = vec!["p3", "p2"]; - - assert_eq!( - StatusCode::SWITCHING_PROTOCOLS, - handshake_with_protocols(&req, &protocols) - .unwrap() - .finish() - .status() - ); - assert_eq!( - Some(&header::HeaderValue::from_static("p2")), - handshake_with_protocols(&req, &protocols) - .unwrap() - .finish() - .headers() - .get(&header::SEC_WEBSOCKET_PROTOCOL) - ); - } -} diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs deleted file mode 100644 index 076e375d3..000000000 --- a/actix-web-actors/tests/test_ws.rs +++ /dev/null @@ -1,67 +0,0 @@ -use actix::prelude::*; -use actix_web::{test, web, App, HttpRequest}; -use actix_web_actors::*; -use bytes::Bytes; -use futures::{SinkExt, StreamExt}; - -struct Ws; - -impl Actor for Ws { - type Context = ws::WebsocketContext; -} - -impl StreamHandler> for Ws { - fn handle( - &mut self, - msg: Result, - ctx: &mut Self::Context, - ) { - match msg.unwrap() { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - ws::Message::Close(reason) => ctx.close(reason), - _ => (), - } - } -} - -#[actix_rt::test] -async fn test_simple() { - let mut srv = test::start(|| { - App::new().service(web::resource("/").to( - |req: HttpRequest, stream: web::Payload| { - async move { ws::start(Ws, &req, stream) } - }, - )) - }); - - // client service - let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); - - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); - - framed - .send(ws::Message::Binary("text".into())) - .await - .unwrap(); - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Binary(Bytes::from_static(b"text").into())); - - framed.send(ws::Message::Ping("text".into())).await.unwrap(); - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Pong(Bytes::copy_from_slice(b"text"))); - - framed - .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - .await - .unwrap(); - - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); -} diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md deleted file mode 100644 index 95696abd3..000000000 --- a/actix-web-codegen/CHANGES.md +++ /dev/null @@ -1,39 +0,0 @@ -# Changes - -## [0.2.NEXT] - 2020-xx-xx - -* Allow the handler function to be named as `config` #1290 - -## [0.2.0] - 2019-12-13 - -* Generate code for actix-web 2.0 - -## [0.1.3] - 2019-10-14 - -* Bump up `syn` & `quote` to 1.0 - -* Provide better error message - -## [0.1.2] - 2019-06-04 - -* Add macros for head, options, trace, connect and patch http methods - -## [0.1.1] - 2019-06-01 - -* Add syn "extra-traits" feature - -## [0.1.0] - 2019-05-18 - -* Release - -## [0.1.0-beta.1] - 2019-04-20 - -* Gen code for actix-web 1.0.0-beta.1 - -## [0.1.0-alpha.6] - 2019-04-14 - -* Gen code for actix-web 1.0.0-alpha.6 - -## [0.1.0-alpha.1] - 2019-03-28 - -* Initial impl diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml deleted file mode 100644 index 3fe561deb..000000000 --- a/actix-web-codegen/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "actix-web-codegen" -version = "0.2.0" -description = "Actix web proc macros" -readme = "README.md" -authors = ["Nikolay Kim "] -license = "MIT/Apache-2.0" -edition = "2018" -workspace = ".." - -[lib] -proc-macro = true - -[dependencies] -quote = "^1" -syn = { version = "^1", features = ["full", "parsing"] } -proc-macro2 = "^1" - -[dev-dependencies] -actix-rt = { version = "1.0.0" } -actix-web = { version = "2.0.0-rc" } -futures = { version = "0.3.1" } diff --git a/actix-web-codegen/LICENSE-APACHE b/actix-web-codegen/LICENSE-APACHE deleted file mode 120000 index 965b606f3..000000000 --- a/actix-web-codegen/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/actix-web-codegen/LICENSE-MIT b/actix-web-codegen/LICENSE-MIT deleted file mode 120000 index 76219eb72..000000000 --- a/actix-web-codegen/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md deleted file mode 100644 index c44a5fc7f..000000000 --- a/actix-web-codegen/README.md +++ /dev/null @@ -1 +0,0 @@ -# Macros for actix-web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web-codegen)](https://crates.io/crates/actix-web-codegen) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs deleted file mode 100644 index 0a727ed69..000000000 --- a/actix-web-codegen/src/lib.rs +++ /dev/null @@ -1,186 +0,0 @@ -#![recursion_limit = "512"] -//! Actix-web codegen module -//! -//! Generators for routes and scopes -//! -//! ## Route -//! -//! Macros: -//! -//! - [get](attr.get.html) -//! - [post](attr.post.html) -//! - [put](attr.put.html) -//! - [delete](attr.delete.html) -//! - [head](attr.head.html) -//! - [connect](attr.connect.html) -//! - [options](attr.options.html) -//! - [trace](attr.trace.html) -//! - [patch](attr.patch.html) -//! -//! ### Attributes: -//! -//! - `"path"` - Raw literal string with path for which to register handle. Mandatory. -//! - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard` -//! -//! ## Notes -//! -//! Function name can be specified as any expression that is going to be accessible to the generate -//! code (e.g `my_guard` or `my_module::my_guard`) -//! -//! ## Example: -//! -//! ```rust -//! use actix_web::HttpResponse; -//! use actix_web_codegen::get; -//! use futures::{future, Future}; -//! -//! #[get("/test")] -//! async fn async_test() -> Result { -//! Ok(HttpResponse::Ok().finish()) -//! } -//! ``` - -extern crate proc_macro; - -mod route; - -use proc_macro::TokenStream; -use syn::parse_macro_input; - -/// Creates route handler with `GET` method guard. -/// -/// Syntax: `#[get("path"[, attributes])]` -/// -/// ## Attributes: -/// -/// - `"path"` - Raw literal string with path for which to register handler. Mandatory. -/// - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard` -#[proc_macro_attribute] -pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = match route::Route::new(args, input, route::GuardType::Get) { - Ok(gen) => gen, - Err(err) => return err.to_compile_error().into(), - }; - gen.generate() -} - -/// Creates route handler with `POST` method guard. -/// -/// Syntax: `#[post("path"[, attributes])]` -/// -/// Attributes are the same as in [get](attr.get.html) -#[proc_macro_attribute] -pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = match route::Route::new(args, input, route::GuardType::Post) { - Ok(gen) => gen, - Err(err) => return err.to_compile_error().into(), - }; - gen.generate() -} - -/// Creates route handler with `PUT` method guard. -/// -/// Syntax: `#[put("path"[, attributes])]` -/// -/// Attributes are the same as in [get](attr.get.html) -#[proc_macro_attribute] -pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = match route::Route::new(args, input, route::GuardType::Put) { - Ok(gen) => gen, - Err(err) => return err.to_compile_error().into(), - }; - gen.generate() -} - -/// Creates route handler with `DELETE` method guard. -/// -/// Syntax: `#[delete("path"[, attributes])]` -/// -/// Attributes are the same as in [get](attr.get.html) -#[proc_macro_attribute] -pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = match route::Route::new(args, input, route::GuardType::Delete) { - Ok(gen) => gen, - Err(err) => return err.to_compile_error().into(), - }; - gen.generate() -} - -/// Creates route handler with `HEAD` method guard. -/// -/// Syntax: `#[head("path"[, attributes])]` -/// -/// Attributes are the same as in [head](attr.head.html) -#[proc_macro_attribute] -pub fn head(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = match route::Route::new(args, input, route::GuardType::Head) { - Ok(gen) => gen, - Err(err) => return err.to_compile_error().into(), - }; - gen.generate() -} - -/// Creates route handler with `CONNECT` method guard. -/// -/// Syntax: `#[connect("path"[, attributes])]` -/// -/// Attributes are the same as in [connect](attr.connect.html) -#[proc_macro_attribute] -pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = match route::Route::new(args, input, route::GuardType::Connect) { - Ok(gen) => gen, - Err(err) => return err.to_compile_error().into(), - }; - gen.generate() -} - -/// Creates route handler with `OPTIONS` method guard. -/// -/// Syntax: `#[options("path"[, attributes])]` -/// -/// Attributes are the same as in [options](attr.options.html) -#[proc_macro_attribute] -pub fn options(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = match route::Route::new(args, input, route::GuardType::Options) { - Ok(gen) => gen, - Err(err) => return err.to_compile_error().into(), - }; - gen.generate() -} - -/// Creates route handler with `TRACE` method guard. -/// -/// Syntax: `#[trace("path"[, attributes])]` -/// -/// Attributes are the same as in [trace](attr.trace.html) -#[proc_macro_attribute] -pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = match route::Route::new(args, input, route::GuardType::Trace) { - Ok(gen) => gen, - Err(err) => return err.to_compile_error().into(), - }; - gen.generate() -} - -/// Creates route handler with `PATCH` method guard. -/// -/// Syntax: `#[patch("path"[, attributes])]` -/// -/// Attributes are the same as in [patch](attr.patch.html) -#[proc_macro_attribute] -pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as syn::AttributeArgs); - let gen = match route::Route::new(args, input, route::GuardType::Patch) { - Ok(gen) => gen, - Err(err) => return err.to_compile_error().into(), - }; - gen.generate() -} diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs deleted file mode 100644 index d48198484..000000000 --- a/actix-web-codegen/src/route.rs +++ /dev/null @@ -1,212 +0,0 @@ -extern crate proc_macro; - -use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::{quote, ToTokens, TokenStreamExt}; -use syn::{AttributeArgs, Ident, NestedMeta}; - -enum ResourceType { - Async, - Sync, -} - -impl ToTokens for ResourceType { - fn to_tokens(&self, stream: &mut TokenStream2) { - let ident = match self { - ResourceType::Async => "to", - ResourceType::Sync => "to", - }; - let ident = Ident::new(ident, Span::call_site()); - stream.append(ident); - } -} - -#[derive(PartialEq)] -pub enum GuardType { - Get, - Post, - Put, - Delete, - Head, - Connect, - Options, - Trace, - Patch, -} - -impl GuardType { - fn as_str(&self) -> &'static str { - match self { - GuardType::Get => "Get", - GuardType::Post => "Post", - GuardType::Put => "Put", - GuardType::Delete => "Delete", - GuardType::Head => "Head", - GuardType::Connect => "Connect", - GuardType::Options => "Options", - GuardType::Trace => "Trace", - GuardType::Patch => "Patch", - } - } -} - -impl ToTokens for GuardType { - fn to_tokens(&self, stream: &mut TokenStream2) { - let ident = self.as_str(); - let ident = Ident::new(ident, Span::call_site()); - stream.append(ident); - } -} - -struct Args { - path: syn::LitStr, - guards: Vec, -} - -impl Args { - fn new(args: AttributeArgs) -> syn::Result { - let mut path = None; - let mut guards = Vec::new(); - for arg in args { - match arg { - NestedMeta::Lit(syn::Lit::Str(lit)) => match path { - None => { - path = Some(lit); - } - _ => { - return Err(syn::Error::new_spanned( - lit, - "Multiple paths specified! Should be only one!", - )); - } - }, - NestedMeta::Meta(syn::Meta::NameValue(nv)) => { - if nv.path.is_ident("guard") { - if let syn::Lit::Str(lit) = nv.lit { - guards.push(Ident::new(&lit.value(), Span::call_site())); - } else { - return Err(syn::Error::new_spanned( - nv.lit, - "Attribute guard expects literal string!", - )); - } - } else { - return Err(syn::Error::new_spanned( - nv.path, - "Unknown attribute key is specified. Allowed: guard", - )); - } - } - arg => { - return Err(syn::Error::new_spanned(arg, "Unknown attribute")); - } - } - } - Ok(Args { - path: path.unwrap(), - guards, - }) - } -} - -pub struct Route { - name: syn::Ident, - args: Args, - ast: syn::ItemFn, - resource_type: ResourceType, - guard: GuardType, -} - -fn guess_resource_type(typ: &syn::Type) -> ResourceType { - let mut guess = ResourceType::Sync; - - if let syn::Type::ImplTrait(typ) = typ { - for bound in typ.bounds.iter() { - if let syn::TypeParamBound::Trait(bound) = bound { - for bound in bound.path.segments.iter() { - if bound.ident == "Future" { - guess = ResourceType::Async; - break; - } else if bound.ident == "Responder" { - guess = ResourceType::Sync; - break; - } - } - } - } - } - - guess -} - -impl Route { - pub fn new( - args: AttributeArgs, - input: TokenStream, - guard: GuardType, - ) -> syn::Result { - if args.is_empty() { - return Err(syn::Error::new( - Span::call_site(), - format!( - r#"invalid server definition, expected #[{}("")]"#, - guard.as_str().to_ascii_lowercase() - ), - )); - } - let ast: syn::ItemFn = syn::parse(input)?; - let name = ast.sig.ident.clone(); - - let args = Args::new(args)?; - - let resource_type = if ast.sig.asyncness.is_some() { - ResourceType::Async - } else { - match ast.sig.output { - syn::ReturnType::Default => { - return Err(syn::Error::new_spanned( - ast, - "Function has no return type. Cannot be used as handler", - )); - } - syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()), - } - }; - - Ok(Self { - name, - args, - ast, - resource_type, - guard, - }) - } - - pub fn generate(&self) -> TokenStream { - let name = &self.name; - let resource_name = name.to_string(); - let guard = &self.guard; - let ast = &self.ast; - let path = &self.args.path; - let extra_guards = &self.args.guards; - let resource_type = &self.resource_type; - let stream = quote! { - #[allow(non_camel_case_types)] - pub struct #name; - - impl actix_web::dev::HttpServiceFactory for #name { - fn register(self, __config: &mut actix_web::dev::AppService) { - #ast - let __resource = actix_web::Resource::new(#path) - .name(#resource_name) - .guard(actix_web::guard::#guard()) - #(.guard(actix_web::guard::fn_guard(#extra_guards)))* - .#resource_type(#name); - - actix_web::dev::HttpServiceFactory::register(__resource, __config) - } - } - }; - stream.into() - } -} diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs deleted file mode 100644 index ffb50c11e..000000000 --- a/actix-web-codegen/tests/test_macro.rs +++ /dev/null @@ -1,157 +0,0 @@ -use actix_web::{http, test, web::Path, App, HttpResponse, Responder}; -use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, trace}; -use futures::{future, Future}; - -// Make sure that we can name function as 'config' -#[get("/config")] -async fn config() -> impl Responder { - HttpResponse::Ok() -} - -#[get("/test")] -async fn test_handler() -> impl Responder { - HttpResponse::Ok() -} - -#[put("/test")] -async fn put_test() -> impl Responder { - HttpResponse::Created() -} - -#[patch("/test")] -async fn patch_test() -> impl Responder { - HttpResponse::Ok() -} - -#[post("/test")] -async fn post_test() -> impl Responder { - HttpResponse::NoContent() -} - -#[head("/test")] -async fn head_test() -> impl Responder { - HttpResponse::Ok() -} - -#[connect("/test")] -async fn connect_test() -> impl Responder { - HttpResponse::Ok() -} - -#[options("/test")] -async fn options_test() -> impl Responder { - HttpResponse::Ok() -} - -#[trace("/test")] -async fn trace_test() -> impl Responder { - HttpResponse::Ok() -} - -#[get("/test")] -fn auto_async() -> impl Future> { - future::ok(HttpResponse::Ok().finish()) -} - -#[get("/test")] -fn auto_sync() -> impl Future> { - future::ok(HttpResponse::Ok().finish()) -} - -#[put("/test/{param}")] -async fn put_param_test(_: Path) -> impl Responder { - HttpResponse::Created() -} - -#[delete("/test/{param}")] -async fn delete_param_test(_: Path) -> impl Responder { - HttpResponse::NoContent() -} - -#[get("/test/{param}")] -async fn get_param_test(_: Path) -> impl Responder { - HttpResponse::Ok() -} - -#[actix_rt::test] -async fn test_params() { - let srv = test::start(|| { - App::new() - .service(get_param_test) - .service(put_param_test) - .service(delete_param_test) - }); - - let request = srv.request(http::Method::GET, srv.url("/test/it")); - let response = request.send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::OK); - - let request = srv.request(http::Method::PUT, srv.url("/test/it")); - let response = request.send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::CREATED); - - let request = srv.request(http::Method::DELETE, srv.url("/test/it")); - let response = request.send().await.unwrap(); - assert_eq!(response.status(), http::StatusCode::NO_CONTENT); -} - -#[actix_rt::test] -async fn test_body() { - let srv = test::start(|| { - App::new() - .service(post_test) - .service(put_test) - .service(head_test) - .service(connect_test) - .service(options_test) - .service(trace_test) - .service(patch_test) - .service(test_handler) - }); - let request = srv.request(http::Method::GET, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - - let request = srv.request(http::Method::HEAD, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - - let request = srv.request(http::Method::CONNECT, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - - let request = srv.request(http::Method::OPTIONS, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - - let request = srv.request(http::Method::TRACE, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - - let request = srv.request(http::Method::PATCH, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - - let request = srv.request(http::Method::PUT, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.status(), http::StatusCode::CREATED); - - let request = srv.request(http::Method::POST, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.status(), http::StatusCode::NO_CONTENT); - - let request = srv.request(http::Method::GET, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); -} - -#[actix_rt::test] -async fn test_auto_async() { - let srv = test::start(|| App::new().service(auto_async)); - - let request = srv.request(http::Method::GET, srv.url("/test")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); -} diff --git a/awc/CHANGES.md b/awc/CHANGES.md deleted file mode 100644 index d9b26e453..000000000 --- a/awc/CHANGES.md +++ /dev/null @@ -1,170 +0,0 @@ -# Changes - -## [1.0.1] - 2019-12-15 - -* Fix compilation with default features off - - -## [1.0.0] - 2019-12-13 - -* Release - -## [1.0.0-alpha.3] - -* Migrate to `std::future` - - -## [0.2.8] - 2019-11-06 - -* Add support for setting query from Serialize type for client request. - - -## [0.2.7] - 2019-09-25 - -### Added - -* Remaining getter methods for `ClientRequest`'s private `head` field #1101 - - -## [0.2.6] - 2019-09-12 - -### Added - -* Export frozen request related types. - - -## [0.2.5] - 2019-09-11 - -### Added - -* Add `FrozenClientRequest` to support retries for sending HTTP requests - -### Changed - -* Ensure that the `Host` header is set when initiating a WebSocket client connection. - - -## [0.2.4] - 2019-08-13 - -### Changed - -* Update percent-encoding to "2.1" - -* Update serde_urlencoded to "0.6.1" - - -## [0.2.3] - 2019-08-01 - -### Added - -* Add `rustls` support - - -## [0.2.2] - 2019-07-01 - -### Changed - -* Always append a colon after username in basic auth - -* Upgrade `rand` dependency version to 0.7 - - -## [0.2.1] - 2019-06-05 - -### Added - -* Add license files - -## [0.2.0] - 2019-05-12 - -### Added - -* Allow to send headers in `Camel-Case` form. - -### Changed - -* Upgrade actix-http dependency. - - -## [0.1.1] - 2019-04-19 - -### Added - -* Allow to specify server address for http and ws requests. - -### Changed - -* `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref - - -## [0.1.0] - 2019-04-16 - -* No changes - - -## [0.1.0-alpha.6] - 2019-04-14 - -### Changed - -* Do not set default headers for websocket request - - -## [0.1.0-alpha.5] - 2019-04-12 - -### Changed - -* Do not set any default headers - -### Added - -* Add Debug impl for BoxedSocket - - -## [0.1.0-alpha.4] - 2019-04-08 - -### Changed - -* Update actix-http dependency - - -## [0.1.0-alpha.3] - 2019-04-02 - -### Added - -* Export `MessageBody` type - -* `ClientResponse::json()` - Loads and parse `application/json` encoded body - - -### Changed - -* `ClientRequest::json()` accepts reference instead of object. - -* `ClientResponse::body()` does not consume response object. - -* Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()` - - -## [0.1.0-alpha.2] - 2019-03-29 - -### Added - -* Per request and session wide request timeout. - -* Session wide headers. - -* Session wide basic and bearer auth. - -* Re-export `actix_http::client::Connector`. - - -### Changed - -* Allow to override request's uri - -* Export `ws` sub-module with websockets related types - - -## [0.1.0-alpha.1] - 2019-03-28 - -* Initial impl diff --git a/awc/Cargo.toml b/awc/Cargo.toml deleted file mode 100644 index 67e0a3ee4..000000000 --- a/awc/Cargo.toml +++ /dev/null @@ -1,68 +0,0 @@ -[package] -name = "awc" -version = "1.0.1" -authors = ["Nikolay Kim "] -description = "Actix http client." -readme = "README.md" -keywords = ["actix", "http", "framework", "async", "web"] -homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-web.git" -documentation = "https://docs.rs/awc/" -categories = ["network-programming", "asynchronous", - "web-programming::http-client", - "web-programming::websocket"] -license = "MIT/Apache-2.0" -edition = "2018" - -[lib] -name = "awc" -path = "src/lib.rs" - -[package.metadata.docs.rs] -features = ["openssl", "rustls", "compress"] - -[features] -default = ["compress"] - -# openssl -openssl = ["open-ssl", "actix-http/openssl"] - -# rustls -rustls = ["rust-tls", "actix-http/rustls"] - -# content-encoding support -compress = ["actix-http/compress"] - -[dependencies] -actix-codec = "0.2.0" -actix-service = "1.0.1" -actix-http = "1.0.0" -actix-rt = "1.0.0" - -base64 = "0.11" -bytes = "0.5.3" -derive_more = "0.99.2" -futures-core = "0.3.1" -log =" 0.4" -mime = "0.3" -percent-encoding = "2.1" -rand = "0.7" -serde = "1.0" -serde_json = "1.0" -serde_urlencoded = "0.6.1" -open-ssl = { version="0.10", package="openssl", optional = true } -rust-tls = { version = "0.16.0", package="rustls", optional = true, features = ["dangerous_configuration"] } - -[dev-dependencies] -actix-connect = { version = "1.0.1", features=["openssl"] } -actix-web = { version = "2.0.0-rc", features=["openssl"] } -actix-http = { version = "1.0.1", features=["openssl"] } -actix-http-test = { version = "1.0.0", features=["openssl"] } -actix-utils = "1.0.3" -actix-server = "1.0.0" -actix-tls = { version = "1.0.0", features=["openssl", "rustls"] } -brotli2 = "0.3.2" -flate2 = "1.0.13" -futures = "0.3.1" -env_logger = "0.6" -webpki = "0.21" diff --git a/awc/LICENSE-APACHE b/awc/LICENSE-APACHE deleted file mode 120000 index 965b606f3..000000000 --- a/awc/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/awc/LICENSE-MIT b/awc/LICENSE-MIT deleted file mode 120000 index 76219eb72..000000000 --- a/awc/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/awc/README.md b/awc/README.md deleted file mode 100644 index 3b0034d76..000000000 --- a/awc/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Actix http client [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/awc)](https://crates.io/crates/awc) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -An HTTP Client - -## Documentation & community resources - -* [User Guide](https://actix.rs/docs/) -* [API Documentation](https://docs.rs/awc/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [awc](https://crates.io/crates/awc) -* Minimum supported Rust version: 1.33 or later - -## Example - -```rust -use actix_rt::System; -use awc::Client; -use futures::future::{Future, lazy}; - -fn main() { - System::new("test").block_on(lazy(|| { - let mut client = Client::default(); - - client.get("http://www.rust-lang.org") // <- Create request builder - .header("User-Agent", "Actix-web") - .send() // <- Send http request - .and_then(|response| { // <- server http response - println!("Response: {:?}", response); - Ok(()) - }) - })); -} -``` diff --git a/awc/src/builder.rs b/awc/src/builder.rs deleted file mode 100644 index 7bd0171ec..000000000 --- a/awc/src/builder.rs +++ /dev/null @@ -1,192 +0,0 @@ -use std::cell::RefCell; -use std::convert::TryFrom; -use std::fmt; -use std::rc::Rc; -use std::time::Duration; - -use actix_http::client::{Connect, ConnectError, Connection, Connector}; -use actix_http::http::{header, Error as HttpError, HeaderMap, HeaderName}; -use actix_service::Service; - -use crate::connect::ConnectorWrapper; -use crate::{Client, ClientConfig}; - -/// An HTTP Client builder -/// -/// This type can be used to construct an instance of `Client` through a -/// builder-like pattern. -pub struct ClientBuilder { - config: ClientConfig, - default_headers: bool, - allow_redirects: bool, - max_redirects: usize, -} - -impl Default for ClientBuilder { - fn default() -> Self { - Self::new() - } -} - -impl ClientBuilder { - pub fn new() -> Self { - ClientBuilder { - default_headers: true, - allow_redirects: true, - max_redirects: 10, - config: ClientConfig { - headers: HeaderMap::new(), - timeout: Some(Duration::from_secs(5)), - connector: RefCell::new(Box::new(ConnectorWrapper( - Connector::new().finish(), - ))), - }, - } - } - - /// Use custom connector service. - pub fn connector(mut self, connector: T) -> Self - where - T: Service + 'static, - T::Response: Connection, - ::Future: 'static, - T::Future: 'static, - { - self.config.connector = RefCell::new(Box::new(ConnectorWrapper(connector))); - self - } - - /// Set request timeout - /// - /// Request timeout is the total time before a response must be received. - /// Default value is 5 seconds. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.config.timeout = Some(timeout); - self - } - - /// Disable request timeout. - pub fn disable_timeout(mut self) -> Self { - self.config.timeout = None; - self - } - - /// Do not follow redirects. - /// - /// Redirects are allowed by default. - pub fn disable_redirects(mut self) -> Self { - self.allow_redirects = false; - self - } - - /// Set max number of redirects. - /// - /// Max redirects is set to 10 by default. - pub fn max_redirects(mut self, num: usize) -> Self { - self.max_redirects = num; - self - } - - /// Do not add default request headers. - /// By default `Date` and `User-Agent` headers are set. - pub fn no_default_headers(mut self) -> Self { - self.default_headers = false; - self - } - - /// Add default header. Headers added by this method - /// get added to every request. - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: fmt::Debug + Into, - V: header::IntoHeaderValue, - V::Error: fmt::Debug, - { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - self.config.headers.append(key, value); - } - Err(e) => log::error!("Header value error: {:?}", e), - }, - Err(e) => log::error!("Header name error: {:?}", e), - } - self - } - - /// Set client wide HTTP basic authorization header - pub fn basic_auth(self, username: U, password: Option<&str>) -> Self - where - U: fmt::Display, - { - let auth = match password { - Some(password) => format!("{}:{}", username, password), - None => format!("{}:", username), - }; - self.header( - header::AUTHORIZATION, - format!("Basic {}", base64::encode(&auth)), - ) - } - - /// Set client wide HTTP bearer authentication header - pub fn bearer_auth(self, token: T) -> Self - where - T: fmt::Display, - { - self.header(header::AUTHORIZATION, format!("Bearer {}", token)) - } - - /// Finish build process and create `Client` instance. - pub fn finish(self) -> Client { - Client(Rc::new(self.config)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn client_basic_auth() { - let client = ClientBuilder::new().basic_auth("username", Some("password")); - assert_eq!( - client - .config - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" - ); - - let client = ClientBuilder::new().basic_auth("username", None); - assert_eq!( - client - .config - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Basic dXNlcm5hbWU6" - ); - } - - #[test] - fn client_bearer_auth() { - let client = ClientBuilder::new().bearer_auth("someS3cr3tAutht0k3n"); - assert_eq!( - client - .config - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Bearer someS3cr3tAutht0k3n" - ); - } -} diff --git a/awc/src/connect.rs b/awc/src/connect.rs deleted file mode 100644 index 618d653f5..000000000 --- a/awc/src/connect.rs +++ /dev/null @@ -1,259 +0,0 @@ -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{fmt, io, mem, net}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_http::body::Body; -use actix_http::client::{ - Connect as ClientConnect, ConnectError, Connection, SendRequestError, -}; -use actix_http::h1::ClientCodec; -use actix_http::http::HeaderMap; -use actix_http::{RequestHead, RequestHeadType, ResponseHead}; -use actix_service::Service; - -use crate::response::ClientResponse; - -pub(crate) struct ConnectorWrapper(pub T); - -pub(crate) trait Connect { - fn send_request( - &mut self, - head: RequestHead, - body: Body, - addr: Option, - ) -> Pin>>>; - - fn send_request_extra( - &mut self, - head: Rc, - extra_headers: Option, - body: Body, - addr: Option, - ) -> Pin>>>; - - /// Send request, returns Response and Framed - fn open_tunnel( - &mut self, - head: RequestHead, - addr: Option, - ) -> Pin< - Box< - dyn Future< - Output = Result< - (ResponseHead, Framed), - SendRequestError, - >, - >, - >, - >; - - /// Send request and extra headers, returns Response and Framed - fn open_tunnel_extra( - &mut self, - head: Rc, - extra_headers: Option, - addr: Option, - ) -> Pin< - Box< - dyn Future< - Output = Result< - (ResponseHead, Framed), - SendRequestError, - >, - >, - >, - >; -} - -impl Connect for ConnectorWrapper -where - T: Service, - T::Response: Connection, - ::Io: 'static, - ::Future: 'static, - ::TunnelFuture: 'static, - T::Future: 'static, -{ - fn send_request( - &mut self, - head: RequestHead, - body: Body, - addr: Option, - ) -> Pin>>> { - // connect to the host - let fut = self.0.call(ClientConnect { - uri: head.uri.clone(), - addr, - }); - - Box::pin(async move { - let connection = fut.await?; - - // send request - connection - .send_request(RequestHeadType::from(head), body) - .await - .map(|(head, payload)| ClientResponse::new(head, payload)) - }) - } - - fn send_request_extra( - &mut self, - head: Rc, - extra_headers: Option, - body: Body, - addr: Option, - ) -> Pin>>> { - // connect to the host - let fut = self.0.call(ClientConnect { - uri: head.uri.clone(), - addr, - }); - - Box::pin(async move { - let connection = fut.await?; - - // send request - let (head, payload) = connection - .send_request(RequestHeadType::Rc(head, extra_headers), body) - .await?; - - Ok(ClientResponse::new(head, payload)) - }) - } - - fn open_tunnel( - &mut self, - head: RequestHead, - addr: Option, - ) -> Pin< - Box< - dyn Future< - Output = Result< - (ResponseHead, Framed), - SendRequestError, - >, - >, - >, - > { - // connect to the host - let fut = self.0.call(ClientConnect { - uri: head.uri.clone(), - addr, - }); - - Box::pin(async move { - let connection = fut.await?; - - // send request - let (head, framed) = - connection.open_tunnel(RequestHeadType::from(head)).await?; - - let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); - Ok((head, framed)) - }) - } - - fn open_tunnel_extra( - &mut self, - head: Rc, - extra_headers: Option, - addr: Option, - ) -> Pin< - Box< - dyn Future< - Output = Result< - (ResponseHead, Framed), - SendRequestError, - >, - >, - >, - > { - // connect to the host - let fut = self.0.call(ClientConnect { - uri: head.uri.clone(), - addr, - }); - - Box::pin(async move { - let connection = fut.await?; - - // send request - let (head, framed) = connection - .open_tunnel(RequestHeadType::Rc(head, extra_headers)) - .await?; - - let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); - Ok((head, framed)) - }) - } -} - -trait AsyncSocket { - fn as_read(&self) -> &(dyn AsyncRead + Unpin); - fn as_read_mut(&mut self) -> &mut (dyn AsyncRead + Unpin); - fn as_write(&mut self) -> &mut (dyn AsyncWrite + Unpin); -} - -struct Socket(T); - -impl AsyncSocket for Socket { - fn as_read(&self) -> &(dyn AsyncRead + Unpin) { - &self.0 - } - fn as_read_mut(&mut self) -> &mut (dyn AsyncRead + Unpin) { - &mut self.0 - } - fn as_write(&mut self) -> &mut (dyn AsyncWrite + Unpin) { - &mut self.0 - } -} - -pub struct BoxedSocket(Box); - -impl fmt::Debug for BoxedSocket { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "BoxedSocket") - } -} - -impl AsyncRead for BoxedSocket { - unsafe fn prepare_uninitialized_buffer( - &self, - buf: &mut [mem::MaybeUninit], - ) -> bool { - self.0.as_read().prepare_uninitialized_buffer(buf) - } - - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - Pin::new(self.get_mut().0.as_read_mut()).poll_read(cx, buf) - } -} - -impl AsyncWrite for BoxedSocket { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Pin::new(self.get_mut().0.as_write()).poll_write(cx, buf) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(self.get_mut().0.as_write()).poll_flush(cx) - } - - fn poll_shutdown( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(self.get_mut().0.as_write()).poll_shutdown(cx) - } -} diff --git a/awc/src/error.rs b/awc/src/error.rs deleted file mode 100644 index 7fece74ee..000000000 --- a/awc/src/error.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Http client errors -pub use actix_http::client::{ - ConnectError, FreezeRequestError, InvalidUrl, SendRequestError, -}; -pub use actix_http::error::PayloadError; -pub use actix_http::http::Error as HttpError; -pub use actix_http::ws::HandshakeError as WsHandshakeError; -pub use actix_http::ws::ProtocolError as WsProtocolError; - -use actix_http::ResponseError; -use serde_json::error::Error as JsonError; - -use actix_http::http::{header::HeaderValue, StatusCode}; -use derive_more::{Display, From}; - -/// Websocket client error -#[derive(Debug, Display, From)] -pub enum WsClientError { - /// Invalid response status - #[display(fmt = "Invalid response status")] - InvalidResponseStatus(StatusCode), - /// Invalid upgrade header - #[display(fmt = "Invalid upgrade header")] - InvalidUpgradeHeader, - /// Invalid connection header - #[display(fmt = "Invalid connection header")] - InvalidConnectionHeader(HeaderValue), - /// Missing CONNECTION header - #[display(fmt = "Missing CONNECTION header")] - MissingConnectionHeader, - /// Missing SEC-WEBSOCKET-ACCEPT header - #[display(fmt = "Missing SEC-WEBSOCKET-ACCEPT header")] - MissingWebSocketAcceptHeader, - /// Invalid challenge response - #[display(fmt = "Invalid challenge response")] - InvalidChallengeResponse(String, HeaderValue), - /// Protocol error - #[display(fmt = "{}", _0)] - Protocol(WsProtocolError), - /// Send request error - #[display(fmt = "{}", _0)] - SendRequest(SendRequestError), -} - -impl From for WsClientError { - fn from(err: InvalidUrl) -> Self { - WsClientError::SendRequest(err.into()) - } -} - -impl From for WsClientError { - fn from(err: HttpError) -> Self { - WsClientError::SendRequest(err.into()) - } -} - -/// A set of errors that can occur during parsing json payloads -#[derive(Debug, Display, From)] -pub enum JsonPayloadError { - /// Content type error - #[display(fmt = "Content type error")] - ContentType, - /// Deserialize error - #[display(fmt = "Json deserialize error: {}", _0)] - Deserialize(JsonError), - /// Payload error - #[display(fmt = "Error that occur during reading payload: {}", _0)] - Payload(PayloadError), -} - -/// Return `InternalServerError` for `JsonPayloadError` -impl ResponseError for JsonPayloadError {} diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs deleted file mode 100644 index f7098863c..000000000 --- a/awc/src/frozen.rs +++ /dev/null @@ -1,236 +0,0 @@ -use std::convert::TryFrom; -use std::net; -use std::rc::Rc; -use std::time::Duration; - -use bytes::Bytes; -use futures_core::Stream; -use serde::Serialize; - -use actix_http::body::Body; -use actix_http::http::header::IntoHeaderValue; -use actix_http::http::{Error as HttpError, HeaderMap, HeaderName, Method, Uri}; -use actix_http::{Error, RequestHead}; - -use crate::sender::{RequestSender, SendClientRequest}; -use crate::ClientConfig; - -/// `FrozenClientRequest` struct represents clonable client request. -/// It could be used to send same request multiple times. -#[derive(Clone)] -pub struct FrozenClientRequest { - pub(crate) head: Rc, - pub(crate) addr: Option, - pub(crate) response_decompress: bool, - pub(crate) timeout: Option, - pub(crate) config: Rc, -} - -impl FrozenClientRequest { - /// Get HTTP URI of request - pub fn get_uri(&self) -> &Uri { - &self.head.uri - } - - /// Get HTTP method of this request - pub fn get_method(&self) -> &Method { - &self.head.method - } - - /// Returns request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.head.headers - } - - /// Send a body. - pub fn send_body(&self, body: B) -> SendClientRequest - where - B: Into, - { - RequestSender::Rc(self.head.clone(), None).send_body( - self.addr, - self.response_decompress, - self.timeout, - self.config.as_ref(), - body, - ) - } - - /// Send a json body. - pub fn send_json(&self, value: &T) -> SendClientRequest { - RequestSender::Rc(self.head.clone(), None).send_json( - self.addr, - self.response_decompress, - self.timeout, - self.config.as_ref(), - value, - ) - } - - /// Send an urlencoded body. - pub fn send_form(&self, value: &T) -> SendClientRequest { - RequestSender::Rc(self.head.clone(), None).send_form( - self.addr, - self.response_decompress, - self.timeout, - self.config.as_ref(), - value, - ) - } - - /// Send a streaming body. - pub fn send_stream(&self, stream: S) -> SendClientRequest - where - S: Stream> + Unpin + 'static, - E: Into + 'static, - { - RequestSender::Rc(self.head.clone(), None).send_stream( - self.addr, - self.response_decompress, - self.timeout, - self.config.as_ref(), - stream, - ) - } - - /// Send an empty body. - pub fn send(&self) -> SendClientRequest { - RequestSender::Rc(self.head.clone(), None).send( - self.addr, - self.response_decompress, - self.timeout, - self.config.as_ref(), - ) - } - - /// Create a `FrozenSendBuilder` with extra headers - pub fn extra_headers(&self, extra_headers: HeaderMap) -> FrozenSendBuilder { - FrozenSendBuilder::new(self.clone(), extra_headers) - } - - /// Create a `FrozenSendBuilder` with an extra header - pub fn extra_header(&self, key: K, value: V) -> FrozenSendBuilder - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - self.extra_headers(HeaderMap::new()) - .extra_header(key, value) - } -} - -/// Builder that allows to modify extra headers. -pub struct FrozenSendBuilder { - req: FrozenClientRequest, - extra_headers: HeaderMap, - err: Option, -} - -impl FrozenSendBuilder { - pub(crate) fn new(req: FrozenClientRequest, extra_headers: HeaderMap) -> Self { - Self { - req, - extra_headers, - err: None, - } - } - - /// Insert a header, it overrides existing header in `FrozenClientRequest`. - pub fn extra_header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => self.extra_headers.insert(key, value), - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Complete request construction and send a body. - pub fn send_body(self, body: B) -> SendClientRequest - where - B: Into, - { - if let Some(e) = self.err { - return e.into(); - } - - RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_body( - self.req.addr, - self.req.response_decompress, - self.req.timeout, - self.req.config.as_ref(), - body, - ) - } - - /// Complete request construction and send a json body. - pub fn send_json(self, value: &T) -> SendClientRequest { - if let Some(e) = self.err { - return e.into(); - } - - RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_json( - self.req.addr, - self.req.response_decompress, - self.req.timeout, - self.req.config.as_ref(), - value, - ) - } - - /// Complete request construction and send an urlencoded body. - pub fn send_form(self, value: &T) -> SendClientRequest { - if let Some(e) = self.err { - return e.into(); - } - - RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_form( - self.req.addr, - self.req.response_decompress, - self.req.timeout, - self.req.config.as_ref(), - value, - ) - } - - /// Complete request construction and send a streaming body. - pub fn send_stream(self, stream: S) -> SendClientRequest - where - S: Stream> + Unpin + 'static, - E: Into + 'static, - { - if let Some(e) = self.err { - return e.into(); - } - - RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_stream( - self.req.addr, - self.req.response_decompress, - self.req.timeout, - self.req.config.as_ref(), - stream, - ) - } - - /// Complete request construction and send an empty body. - pub fn send(self) -> SendClientRequest { - if let Some(e) = self.err { - return e.into(); - } - - RequestSender::Rc(self.req.head, Some(self.extra_headers)).send( - self.req.addr, - self.req.response_decompress, - self.req.timeout, - self.req.config.as_ref(), - ) - } -} diff --git a/awc/src/lib.rs b/awc/src/lib.rs deleted file mode 100644 index 8944fe229..000000000 --- a/awc/src/lib.rs +++ /dev/null @@ -1,209 +0,0 @@ -#![deny(rust_2018_idioms, warnings)] -#![allow( - clippy::type_complexity, - clippy::borrow_interior_mutable_const, - clippy::needless_doctest_main -)] -//! An HTTP Client -//! -//! ```rust -//! use futures::future::{lazy, Future}; -//! use actix_rt::System; -//! use awc::Client; -//! -//! #[actix_rt::main] -//! async fn main() { -//! let mut client = Client::default(); -//! -//! let response = client.get("http://www.rust-lang.org") // <- Create request builder -//! .header("User-Agent", "Actix-web") -//! .send() // <- Send http request -//! .await; -//! -//! println!("Response: {:?}", response); -//! } -//! ``` -use std::cell::RefCell; -use std::convert::TryFrom; -use std::rc::Rc; -use std::time::Duration; - -pub use actix_http::{client::Connector, cookie, http}; - -use actix_http::http::{Error as HttpError, HeaderMap, Method, Uri}; -use actix_http::RequestHead; - -mod builder; -mod connect; -pub mod error; -mod frozen; -mod request; -mod response; -mod sender; -pub mod test; -pub mod ws; - -pub use self::builder::ClientBuilder; -pub use self::connect::BoxedSocket; -pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder}; -pub use self::request::ClientRequest; -pub use self::response::{ClientResponse, JsonBody, MessageBody}; -pub use self::sender::SendClientRequest; - -use self::connect::{Connect, ConnectorWrapper}; - -/// An HTTP Client -/// -/// ```rust -/// use awc::Client; -/// -/// #[actix_rt::main] -/// async fn main() { -/// let mut client = Client::default(); -/// -/// let res = client.get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .send() // <- Send http request -/// .await; // <- send request and wait for response -/// -/// println!("Response: {:?}", res); -/// } -/// ``` -#[derive(Clone)] -pub struct Client(Rc); - -pub(crate) struct ClientConfig { - pub(crate) connector: RefCell>, - pub(crate) headers: HeaderMap, - pub(crate) timeout: Option, -} - -impl Default for Client { - fn default() -> Self { - Client(Rc::new(ClientConfig { - connector: RefCell::new(Box::new(ConnectorWrapper( - Connector::new().finish(), - ))), - headers: HeaderMap::new(), - timeout: Some(Duration::from_secs(5)), - })) - } -} - -impl Client { - /// Create new client instance with default settings. - pub fn new() -> Client { - Client::default() - } - - /// Build client instance. - pub fn build() -> ClientBuilder { - ClientBuilder::new() - } - - /// Construct HTTP request. - pub fn request(&self, method: Method, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - let mut req = ClientRequest::new(method, url, self.0.clone()); - - for (key, value) in self.0.headers.iter() { - req = req.set_header_if_none(key.clone(), value.clone()); - } - req - } - - /// Create `ClientRequest` from `RequestHead` - /// - /// It is useful for proxy requests. This implementation - /// copies all headers and the method. - pub fn request_from(&self, url: U, head: &RequestHead) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - let mut req = self.request(head.method.clone(), url); - for (key, value) in head.headers.iter() { - req = req.set_header_if_none(key.clone(), value.clone()); - } - req - } - - /// Construct HTTP *GET* request. - pub fn get(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::GET, url) - } - - /// Construct HTTP *HEAD* request. - pub fn head(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::HEAD, url) - } - - /// Construct HTTP *PUT* request. - pub fn put(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::PUT, url) - } - - /// Construct HTTP *POST* request. - pub fn post(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::POST, url) - } - - /// Construct HTTP *PATCH* request. - pub fn patch(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::PATCH, url) - } - - /// Construct HTTP *DELETE* request. - pub fn delete(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::DELETE, url) - } - - /// Construct HTTP *OPTIONS* request. - pub fn options(&self, url: U) -> ClientRequest - where - Uri: TryFrom, - >::Error: Into, - { - self.request(Method::OPTIONS, url) - } - - /// Construct WebSockets request. - pub fn ws(&self, url: U) -> ws::WebsocketsRequest - where - Uri: TryFrom, - >::Error: Into, - { - let mut req = ws::WebsocketsRequest::new(url, self.0.clone()); - for (key, value) in self.0.headers.iter() { - req.head.headers.insert(key.clone(), value.clone()); - } - req - } -} diff --git a/awc/src/request.rs b/awc/src/request.rs deleted file mode 100644 index 67b063a8e..000000000 --- a/awc/src/request.rs +++ /dev/null @@ -1,710 +0,0 @@ -use std::convert::TryFrom; -use std::fmt::Write as FmtWrite; -use std::rc::Rc; -use std::time::Duration; -use std::{fmt, net}; - -use bytes::Bytes; -use futures_core::Stream; -use percent_encoding::percent_encode; -use serde::Serialize; - -use actix_http::body::Body; -use actix_http::cookie::{Cookie, CookieJar, USERINFO}; -use actix_http::http::header::{self, Header, IntoHeaderValue}; -use actix_http::http::{ - uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue, Method, - Uri, Version, -}; -use actix_http::{Error, RequestHead}; - -use crate::error::{FreezeRequestError, InvalidUrl}; -use crate::frozen::FrozenClientRequest; -use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest}; -use crate::ClientConfig; - -#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -const HTTPS_ENCODING: &str = "br, gzip, deflate"; -#[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))] -const HTTPS_ENCODING: &str = "br"; - -/// An HTTP Client request builder -/// -/// This type can be used to construct an instance of `ClientRequest` through a -/// builder-like pattern. -/// -/// ```rust -/// use actix_rt::System; -/// -/// #[actix_rt::main] -/// async fn main() { -/// let response = awc::Client::new() -/// .get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") -/// .send() // <- Send http request -/// .await; -/// -/// response.and_then(|response| { // <- server http response -/// println!("Response: {:?}", response); -/// Ok(()) -/// }); -/// } -/// ``` -pub struct ClientRequest { - pub(crate) head: RequestHead, - err: Option, - addr: Option, - cookies: Option, - response_decompress: bool, - timeout: Option, - config: Rc, -} - -impl ClientRequest { - /// Create new client request builder. - pub(crate) fn new(method: Method, uri: U, config: Rc) -> Self - where - Uri: TryFrom, - >::Error: Into, - { - ClientRequest { - config, - head: RequestHead::default(), - err: None, - addr: None, - cookies: None, - timeout: None, - response_decompress: true, - } - .method(method) - .uri(uri) - } - - /// Set HTTP URI of request. - #[inline] - pub fn uri(mut self, uri: U) -> Self - where - Uri: TryFrom, - >::Error: Into, - { - match Uri::try_from(uri) { - Ok(uri) => self.head.uri = uri, - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Get HTTP URI of request. - pub fn get_uri(&self) -> &Uri { - &self.head.uri - } - - /// Set socket address of the server. - /// - /// This address is used for connection. If address is not - /// provided url's host name get resolved. - pub fn address(mut self, addr: net::SocketAddr) -> Self { - self.addr = Some(addr); - self - } - - /// Set HTTP method of this request. - #[inline] - pub fn method(mut self, method: Method) -> Self { - self.head.method = method; - self - } - - /// Get HTTP method of this request - pub fn get_method(&self) -> &Method { - &self.head.method - } - - #[doc(hidden)] - /// Set HTTP version of this request. - /// - /// By default requests's HTTP version depends on network stream - #[inline] - pub fn version(mut self, version: Version) -> Self { - self.head.version = version; - self - } - - /// Get HTTP version of this request. - pub fn get_version(&self) -> &Version { - &self.head.version - } - - /// Get peer address of this request. - pub fn get_peer_addr(&self) -> &Option { - &self.head.peer_addr - } - - #[inline] - /// Returns request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.head.headers - } - - #[inline] - /// Returns request's mutable headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head.headers - } - - /// Set a header. - /// - /// ```rust - /// fn main() { - /// # actix_rt::System::new("test").block_on(futures::future::lazy(|_| { - /// let req = awc::Client::new() - /// .get("http://www.rust-lang.org") - /// .set(awc::http::header::Date::now()) - /// .set(awc::http::header::ContentType(mime::TEXT_HTML)); - /// # Ok::<_, ()>(()) - /// # })); - /// } - /// ``` - pub fn set(mut self, hdr: H) -> Self { - match hdr.try_into() { - Ok(value) => { - self.head.headers.insert(H::name(), value); - } - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Append a header. - /// - /// Header gets appended to existing header. - /// To override header use `set_header()` method. - /// - /// ```rust - /// use awc::{http, Client}; - /// - /// fn main() { - /// # actix_rt::System::new("test").block_on(async { - /// let req = Client::new() - /// .get("http://www.rust-lang.org") - /// .header("X-TEST", "value") - /// .header(http::header::CONTENT_TYPE, "application/json"); - /// # Ok::<_, ()>(()) - /// # }); - /// } - /// ``` - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => self.head.headers.append(key, value), - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Insert a header, replaces existing header. - pub fn set_header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => self.head.headers.insert(key, value), - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Insert a header only if it is not yet set. - pub fn set_header_if_none(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - match HeaderName::try_from(key) { - Ok(key) => { - if !self.head.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => self.head.headers.insert(key, value), - Err(e) => self.err = Some(e.into()), - } - } - } - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Send headers in `Camel-Case` form. - #[inline] - pub fn camel_case(mut self) -> Self { - self.head.set_camel_case_headers(true); - self - } - - /// Force close connection instead of returning it back to connections pool. - /// This setting affect only http/1 connections. - #[inline] - pub fn force_close(mut self) -> Self { - self.head.set_connection_type(ConnectionType::Close); - self - } - - /// Set request's content type - #[inline] - pub fn content_type(mut self, value: V) -> Self - where - HeaderValue: TryFrom, - >::Error: Into, - { - match HeaderValue::try_from(value) { - Ok(value) => self.head.headers.insert(header::CONTENT_TYPE, value), - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Set content length - #[inline] - pub fn content_length(self, len: u64) -> Self { - self.header(header::CONTENT_LENGTH, len) - } - - /// Set HTTP basic authorization header - pub fn basic_auth(self, username: U, password: Option<&str>) -> Self - where - U: fmt::Display, - { - let auth = match password { - Some(password) => format!("{}:{}", username, password), - None => format!("{}:", username), - }; - self.header( - header::AUTHORIZATION, - format!("Basic {}", base64::encode(&auth)), - ) - } - - /// Set HTTP bearer authentication header - pub fn bearer_auth(self, token: T) -> Self - where - T: fmt::Display, - { - self.header(header::AUTHORIZATION, format!("Bearer {}", token)) - } - - /// Set a cookie - /// - /// ```rust - /// #[actix_rt::main] - /// async fn main() { - /// let resp = awc::Client::new().get("https://www.rust-lang.org") - /// .cookie( - /// awc::http::Cookie::build("name", "value") - /// .domain("www.rust-lang.org") - /// .path("/") - /// .secure(true) - /// .http_only(true) - /// .finish(), - /// ) - /// .send() - /// .await; - /// - /// println!("Response: {:?}", resp); - /// } - /// ``` - pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { - if self.cookies.is_none() { - let mut jar = CookieJar::new(); - jar.add(cookie.into_owned()); - self.cookies = Some(jar) - } else { - self.cookies.as_mut().unwrap().add(cookie.into_owned()); - } - self - } - - /// Disable automatic decompress of response's body - pub fn no_decompress(mut self) -> Self { - self.response_decompress = false; - self - } - - /// Set request timeout. Overrides client wide timeout setting. - /// - /// Request timeout is the total time before a response must be received. - /// Default value is 5 seconds. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = Some(timeout); - self - } - - /// This method calls provided closure with builder reference if - /// value is `true`. - pub fn if_true(self, value: bool, f: F) -> Self - where - F: FnOnce(ClientRequest) -> ClientRequest, - { - if value { - f(self) - } else { - self - } - } - - /// This method calls provided closure with builder reference if - /// value is `Some`. - pub fn if_some(self, value: Option, f: F) -> Self - where - F: FnOnce(T, ClientRequest) -> ClientRequest, - { - if let Some(val) = value { - f(val, self) - } else { - self - } - } - - /// Sets the query part of the request - pub fn query( - mut self, - query: &T, - ) -> Result { - let mut parts = self.head.uri.clone().into_parts(); - - if let Some(path_and_query) = parts.path_and_query { - let query = serde_urlencoded::to_string(query)?; - let path = path_and_query.path(); - parts.path_and_query = format!("{}?{}", path, query).parse().ok(); - - match Uri::from_parts(parts) { - Ok(uri) => self.head.uri = uri, - Err(e) => self.err = Some(e.into()), - } - } - - Ok(self) - } - - /// Freeze request builder and construct `FrozenClientRequest`, - /// which could be used for sending same request multiple times. - pub fn freeze(self) -> Result { - let slf = match self.prep_for_sending() { - Ok(slf) => slf, - Err(e) => return Err(e.into()), - }; - - let request = FrozenClientRequest { - head: Rc::new(slf.head), - addr: slf.addr, - response_decompress: slf.response_decompress, - timeout: slf.timeout, - config: slf.config, - }; - - Ok(request) - } - - /// Complete request construction and send body. - pub fn send_body(self, body: B) -> SendClientRequest - where - B: Into, - { - let slf = match self.prep_for_sending() { - Ok(slf) => slf, - Err(e) => return e.into(), - }; - - RequestSender::Owned(slf.head).send_body( - slf.addr, - slf.response_decompress, - slf.timeout, - slf.config.as_ref(), - body, - ) - } - - /// Set a JSON body and generate `ClientRequest` - pub fn send_json(self, value: &T) -> SendClientRequest { - let slf = match self.prep_for_sending() { - Ok(slf) => slf, - Err(e) => return e.into(), - }; - - RequestSender::Owned(slf.head).send_json( - slf.addr, - slf.response_decompress, - slf.timeout, - slf.config.as_ref(), - value, - ) - } - - /// Set a urlencoded body and generate `ClientRequest` - /// - /// `ClientRequestBuilder` can not be used after this call. - pub fn send_form(self, value: &T) -> SendClientRequest { - let slf = match self.prep_for_sending() { - Ok(slf) => slf, - Err(e) => return e.into(), - }; - - RequestSender::Owned(slf.head).send_form( - slf.addr, - slf.response_decompress, - slf.timeout, - slf.config.as_ref(), - value, - ) - } - - /// Set an streaming body and generate `ClientRequest`. - pub fn send_stream(self, stream: S) -> SendClientRequest - where - S: Stream> + Unpin + 'static, - E: Into + 'static, - { - let slf = match self.prep_for_sending() { - Ok(slf) => slf, - Err(e) => return e.into(), - }; - - RequestSender::Owned(slf.head).send_stream( - slf.addr, - slf.response_decompress, - slf.timeout, - slf.config.as_ref(), - stream, - ) - } - - /// Set an empty body and generate `ClientRequest`. - pub fn send(self) -> SendClientRequest { - let slf = match self.prep_for_sending() { - Ok(slf) => slf, - Err(e) => return e.into(), - }; - - RequestSender::Owned(slf.head).send( - slf.addr, - slf.response_decompress, - slf.timeout, - slf.config.as_ref(), - ) - } - - fn prep_for_sending(mut self) -> Result { - if let Some(e) = self.err { - return Err(e.into()); - } - - // validate uri - let uri = &self.head.uri; - if uri.host().is_none() { - return Err(InvalidUrl::MissingHost.into()); - } else if uri.scheme().is_none() { - return Err(InvalidUrl::MissingScheme.into()); - } else if let Some(scheme) = uri.scheme() { - match scheme.as_str() { - "http" | "ws" | "https" | "wss" => (), - _ => return Err(InvalidUrl::UnknownScheme.into()), - } - } else { - return Err(InvalidUrl::UnknownScheme.into()); - } - - // set cookies - if let Some(ref mut jar) = self.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO); - let value = percent_encode(c.value().as_bytes(), USERINFO); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - self.head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); - } - - let mut slf = self; - - if slf.response_decompress { - let https = slf - .head - .uri - .scheme() - .map(|s| s == &uri::Scheme::HTTPS) - .unwrap_or(true); - - if https { - slf = slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING) - } else { - #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] - { - slf = - slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") - } - }; - } - - Ok(slf) - } -} - -impl fmt::Debug for ClientRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!( - f, - "\nClientRequest {:?} {}:{}", - self.head.version, self.head.method, self.head.uri - )?; - writeln!(f, " headers:")?; - for (key, val) in self.head.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use std::time::SystemTime; - - use super::*; - use crate::Client; - - #[test] - fn test_debug() { - let request = Client::new().get("/").header("x-test", "111"); - let repr = format!("{:?}", request); - assert!(repr.contains("ClientRequest")); - assert!(repr.contains("x-test")); - } - - #[test] - fn test_basics() { - let mut req = Client::new() - .put("/") - .version(Version::HTTP_2) - .set(header::Date(SystemTime::now().into())) - .content_type("plain/text") - .if_true(true, |req| req.header(header::SERVER, "awc")) - .if_true(false, |req| req.header(header::EXPECT, "awc")) - .if_some(Some("server"), |val, req| { - req.header(header::USER_AGENT, val) - }) - .if_some(Option::<&str>::None, |_, req| { - req.header(header::ALLOW, "1") - }) - .content_length(100); - assert!(req.headers().contains_key(header::CONTENT_TYPE)); - assert!(req.headers().contains_key(header::DATE)); - assert!(req.headers().contains_key(header::SERVER)); - assert!(req.headers().contains_key(header::USER_AGENT)); - assert!(!req.headers().contains_key(header::ALLOW)); - assert!(!req.headers().contains_key(header::EXPECT)); - assert_eq!(req.head.version, Version::HTTP_2); - let _ = req.headers_mut(); - let _ = req.send_body(""); - } - - #[test] - fn test_client_header() { - let req = Client::build() - .header(header::CONTENT_TYPE, "111") - .finish() - .get("/"); - - assert_eq!( - req.head - .headers - .get(header::CONTENT_TYPE) - .unwrap() - .to_str() - .unwrap(), - "111" - ); - } - - #[test] - fn test_client_header_override() { - let req = Client::build() - .header(header::CONTENT_TYPE, "111") - .finish() - .get("/") - .set_header(header::CONTENT_TYPE, "222"); - - assert_eq!( - req.head - .headers - .get(header::CONTENT_TYPE) - .unwrap() - .to_str() - .unwrap(), - "222" - ); - } - - #[test] - fn client_basic_auth() { - let req = Client::new() - .get("/") - .basic_auth("username", Some("password")); - assert_eq!( - req.head - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" - ); - - let req = Client::new().get("/").basic_auth("username", None); - assert_eq!( - req.head - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Basic dXNlcm5hbWU6" - ); - } - - #[test] - fn client_bearer_auth() { - let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n"); - assert_eq!( - req.head - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Bearer someS3cr3tAutht0k3n" - ); - } - - #[test] - fn client_query() { - let req = Client::new() - .get("/") - .query(&[("key1", "val1"), ("key2", "val2")]) - .unwrap(); - assert_eq!(req.get_uri().query().unwrap(), "key1=val1&key2=val2"); - } -} diff --git a/awc/src/response.rs b/awc/src/response.rs deleted file mode 100644 index 20093c72d..000000000 --- a/awc/src/response.rs +++ /dev/null @@ -1,468 +0,0 @@ -use std::cell::{Ref, RefMut}; -use std::fmt; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use bytes::{Bytes, BytesMut}; -use futures_core::{ready, Future, Stream}; - -use actix_http::cookie::Cookie; -use actix_http::error::{CookieParseError, PayloadError}; -use actix_http::http::header::{CONTENT_LENGTH, SET_COOKIE}; -use actix_http::http::{HeaderMap, StatusCode, Version}; -use actix_http::{Extensions, HttpMessage, Payload, PayloadStream, ResponseHead}; -use serde::de::DeserializeOwned; - -use crate::error::JsonPayloadError; - -/// Client Response -pub struct ClientResponse { - pub(crate) head: ResponseHead, - pub(crate) payload: Payload, -} - -impl HttpMessage for ClientResponse { - type Stream = S; - - fn headers(&self) -> &HeaderMap { - &self.head.headers - } - - fn extensions(&self) -> Ref<'_, Extensions> { - self.head.extensions() - } - - fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.head.extensions_mut() - } - - fn take_payload(&mut self) -> Payload { - std::mem::replace(&mut self.payload, Payload::None) - } - - /// Load request cookies. - #[inline] - fn cookies(&self) -> Result>>, CookieParseError> { - struct Cookies(Vec>); - - if self.extensions().get::().is_none() { - let mut cookies = Vec::new(); - for hdr in self.headers().get_all(&SET_COOKIE) { - let s = std::str::from_utf8(hdr.as_bytes()) - .map_err(CookieParseError::from)?; - cookies.push(Cookie::parse_encoded(s)?.into_owned()); - } - self.extensions_mut().insert(Cookies(cookies)); - } - Ok(Ref::map(self.extensions(), |ext| { - &ext.get::().unwrap().0 - })) - } -} - -impl ClientResponse { - /// Create new Request instance - pub(crate) fn new(head: ResponseHead, payload: Payload) -> Self { - ClientResponse { head, payload } - } - - #[inline] - pub(crate) fn head(&self) -> &ResponseHead { - &self.head - } - - /// Read the Request Version. - #[inline] - pub fn version(&self) -> Version { - self.head().version - } - - /// Get the status from the server. - #[inline] - pub fn status(&self) -> StatusCode { - self.head().status - } - - #[inline] - /// Returns request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.head().headers - } - - /// Set a body and return previous body value - pub fn map_body(mut self, f: F) -> ClientResponse - where - F: FnOnce(&mut ResponseHead, Payload) -> Payload, - { - let payload = f(&mut self.head, self.payload); - - ClientResponse { - payload, - head: self.head, - } - } -} - -impl ClientResponse -where - S: Stream>, -{ - /// Loads http response's body. - pub fn body(&mut self) -> MessageBody { - MessageBody::new(self) - } - - /// Loads and parse `application/json` encoded body. - /// Return `JsonBody` future. It resolves to a `T` value. - /// - /// Returns error: - /// - /// * content type is not `application/json` - /// * content length is greater than 256k - pub fn json(&mut self) -> JsonBody { - JsonBody::new(self) - } -} - -impl Stream for ClientResponse -where - S: Stream> + Unpin, -{ - type Item = Result; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(&mut self.get_mut().payload).poll_next(cx) - } -} - -impl fmt::Debug for ClientResponse { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "\nClientResponse {:?} {}", self.version(), self.status(),)?; - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -/// Future that resolves to a complete http message body. -pub struct MessageBody { - length: Option, - err: Option, - fut: Option>, -} - -impl MessageBody -where - S: Stream>, -{ - /// Create `MessageBody` for request. - pub fn new(res: &mut ClientResponse) -> MessageBody { - let mut len = None; - if let Some(l) = res.headers().get(&CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } else { - return Self::err(PayloadError::UnknownLength); - } - } else { - return Self::err(PayloadError::UnknownLength); - } - } - - MessageBody { - length: len, - err: None, - fut: Some(ReadBody::new(res.take_payload(), 262_144)), - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - if let Some(ref mut fut) = self.fut { - fut.limit = limit; - } - self - } - - fn err(e: PayloadError) -> Self { - MessageBody { - fut: None, - err: Some(e), - length: None, - } - } -} - -impl Future for MessageBody -where - S: Stream> + Unpin, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - - if let Some(err) = this.err.take() { - return Poll::Ready(Err(err)); - } - - if let Some(len) = this.length.take() { - if len > this.fut.as_ref().unwrap().limit { - return Poll::Ready(Err(PayloadError::Overflow)); - } - } - - Pin::new(&mut this.fut.as_mut().unwrap()).poll(cx) - } -} - -/// Response's payload json parser, it resolves to a deserialized `T` value. -/// -/// Returns error: -/// -/// * content type is not `application/json` -/// * content length is greater than 64k -pub struct JsonBody { - length: Option, - err: Option, - fut: Option>, - _t: PhantomData, -} - -impl JsonBody -where - S: Stream>, - U: DeserializeOwned, -{ - /// Create `JsonBody` for request. - pub fn new(req: &mut ClientResponse) -> Self { - // check content-type - let json = if let Ok(Some(mime)) = req.mime_type() { - mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) - } else { - false - }; - if !json { - return JsonBody { - length: None, - fut: None, - err: Some(JsonPayloadError::ContentType), - _t: PhantomData, - }; - } - - let mut len = None; - if let Some(l) = req.headers().get(&CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } - } - } - - JsonBody { - length: len, - err: None, - fut: Some(ReadBody::new(req.take_payload(), 65536)), - _t: PhantomData, - } - } - - /// Change max size of payload. By default max size is 64Kb - pub fn limit(mut self, limit: usize) -> Self { - if let Some(ref mut fut) = self.fut { - fut.limit = limit; - } - self - } -} - -impl Unpin for JsonBody -where - T: Stream> + Unpin, - U: DeserializeOwned, -{ -} - -impl Future for JsonBody -where - T: Stream> + Unpin, - U: DeserializeOwned, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if let Some(err) = self.err.take() { - return Poll::Ready(Err(err)); - } - - if let Some(len) = self.length.take() { - if len > self.fut.as_ref().unwrap().limit { - return Poll::Ready(Err(JsonPayloadError::Payload( - PayloadError::Overflow, - ))); - } - } - - let body = ready!(Pin::new(&mut self.get_mut().fut.as_mut().unwrap()).poll(cx))?; - Poll::Ready(serde_json::from_slice::(&body).map_err(JsonPayloadError::from)) - } -} - -struct ReadBody { - stream: Payload, - buf: BytesMut, - limit: usize, -} - -impl ReadBody { - fn new(stream: Payload, limit: usize) -> Self { - Self { - stream, - buf: BytesMut::with_capacity(std::cmp::min(limit, 32768)), - limit, - } - } -} - -impl Future for ReadBody -where - S: Stream> + Unpin, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - - loop { - return match Pin::new(&mut this.stream).poll_next(cx)? { - Poll::Ready(Some(chunk)) => { - if (this.buf.len() + chunk.len()) > this.limit { - Poll::Ready(Err(PayloadError::Overflow)) - } else { - this.buf.extend_from_slice(&chunk); - continue; - } - } - Poll::Ready(None) => Poll::Ready(Ok(this.buf.split().freeze())), - Poll::Pending => Poll::Pending, - }; - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde::{Deserialize, Serialize}; - - use crate::{http::header, test::TestResponse}; - - #[actix_rt::test] - async fn test_body() { - let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); - match req.body().await.err().unwrap() { - PayloadError::UnknownLength => (), - _ => unreachable!("error"), - } - - let mut req = - TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); - match req.body().await.err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } - - let mut req = TestResponse::default() - .set_payload(Bytes::from_static(b"test")) - .finish(); - assert_eq!(req.body().await.ok().unwrap(), Bytes::from_static(b"test")); - - let mut req = TestResponse::default() - .set_payload(Bytes::from_static(b"11111111111111")) - .finish(); - match req.body().limit(5).await.err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } - } - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct MyObject { - name: String, - } - - fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { - match err { - JsonPayloadError::Payload(PayloadError::Overflow) => match other { - JsonPayloadError::Payload(PayloadError::Overflow) => true, - _ => false, - }, - JsonPayloadError::ContentType => match other { - JsonPayloadError::ContentType => true, - _ => false, - }, - _ => false, - } - } - - #[actix_rt::test] - async fn test_json_body() { - let mut req = TestResponse::default().finish(); - let json = JsonBody::<_, MyObject>::new(&mut req).await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - - let mut req = TestResponse::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/text"), - ) - .finish(); - let json = JsonBody::<_, MyObject>::new(&mut req).await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - - let mut req = TestResponse::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000"), - ) - .finish(); - - let json = JsonBody::<_, MyObject>::new(&mut req).limit(100).await; - assert!(json_eq( - json.err().unwrap(), - JsonPayloadError::Payload(PayloadError::Overflow) - )); - - let mut req = TestResponse::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .finish(); - - let json = JsonBody::<_, MyObject>::new(&mut req).await; - assert_eq!( - json.ok().unwrap(), - MyObject { - name: "test".to_owned() - } - ); - } -} diff --git a/awc/src/sender.rs b/awc/src/sender.rs deleted file mode 100644 index ec18f12e3..000000000 --- a/awc/src/sender.rs +++ /dev/null @@ -1,325 +0,0 @@ -use std::net; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::time::Duration; - -use actix_rt::time::{delay_for, Delay}; -use bytes::Bytes; -use derive_more::From; -use futures_core::{Future, Stream}; -use serde::Serialize; -use serde_json; - -use actix_http::body::{Body, BodyStream}; -use actix_http::http::header::{self, IntoHeaderValue}; -use actix_http::http::{Error as HttpError, HeaderMap, HeaderName}; -use actix_http::{Error, RequestHead}; - -#[cfg(feature = "compress")] -use actix_http::encoding::Decoder; -#[cfg(feature = "compress")] -use actix_http::http::header::ContentEncoding; -#[cfg(feature = "compress")] -use actix_http::{Payload, PayloadStream}; - -use crate::error::{FreezeRequestError, InvalidUrl, SendRequestError}; -use crate::response::ClientResponse; -use crate::ClientConfig; - -#[derive(Debug, From)] -pub(crate) enum PrepForSendingError { - Url(InvalidUrl), - Http(HttpError), -} - -impl Into for PrepForSendingError { - fn into(self) -> FreezeRequestError { - match self { - PrepForSendingError::Url(e) => FreezeRequestError::Url(e), - PrepForSendingError::Http(e) => FreezeRequestError::Http(e), - } - } -} - -impl Into for PrepForSendingError { - fn into(self) -> SendRequestError { - match self { - PrepForSendingError::Url(e) => SendRequestError::Url(e), - PrepForSendingError::Http(e) => SendRequestError::Http(e), - } - } -} - -/// Future that sends request's payload and resolves to a server response. -#[must_use = "futures do nothing unless polled"] -pub enum SendClientRequest { - Fut( - Pin>>>, - Option, - bool, - ), - Err(Option), -} - -impl SendClientRequest { - pub(crate) fn new( - send: Pin>>>, - response_decompress: bool, - timeout: Option, - ) -> SendClientRequest { - let delay = timeout.map(delay_for); - SendClientRequest::Fut(send, delay, response_decompress) - } -} - -#[cfg(feature = "compress")] -impl Future for SendClientRequest { - type Output = - Result>>, SendRequestError>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - - match this { - SendClientRequest::Fut(send, delay, response_decompress) => { - if delay.is_some() { - match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => (), - _ => return Poll::Ready(Err(SendRequestError::Timeout)), - } - } - - let res = futures_core::ready!(Pin::new(send).poll(cx)).map(|res| { - res.map_body(|head, payload| { - if *response_decompress { - Payload::Stream(Decoder::from_headers( - payload, - &head.headers, - )) - } else { - Payload::Stream(Decoder::new( - payload, - ContentEncoding::Identity, - )) - } - }) - }); - - Poll::Ready(res) - } - SendClientRequest::Err(ref mut e) => match e.take() { - Some(e) => Poll::Ready(Err(e)), - None => panic!("Attempting to call completed future"), - }, - } - } -} - -#[cfg(not(feature = "compress"))] -impl Future for SendClientRequest { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.get_mut(); - match this { - SendClientRequest::Fut(send, delay, _) => { - if delay.is_some() { - match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => (), - _ => return Poll::Ready(Err(SendRequestError::Timeout)), - } - } - Pin::new(send).poll(cx) - } - SendClientRequest::Err(ref mut e) => match e.take() { - Some(e) => Poll::Ready(Err(e)), - None => panic!("Attempting to call completed future"), - }, - } - } -} - -impl From for SendClientRequest { - fn from(e: SendRequestError) -> Self { - SendClientRequest::Err(Some(e)) - } -} - -impl From for SendClientRequest { - fn from(e: Error) -> Self { - SendClientRequest::Err(Some(e.into())) - } -} - -impl From for SendClientRequest { - fn from(e: HttpError) -> Self { - SendClientRequest::Err(Some(e.into())) - } -} - -impl From for SendClientRequest { - fn from(e: PrepForSendingError) -> Self { - SendClientRequest::Err(Some(e.into())) - } -} - -#[derive(Debug)] -pub(crate) enum RequestSender { - Owned(RequestHead), - Rc(Rc, Option), -} - -impl RequestSender { - pub(crate) fn send_body( - self, - addr: Option, - response_decompress: bool, - timeout: Option, - config: &ClientConfig, - body: B, - ) -> SendClientRequest - where - B: Into, - { - let mut connector = config.connector.borrow_mut(); - - let fut = match self { - RequestSender::Owned(head) => { - connector.send_request(head, body.into(), addr) - } - RequestSender::Rc(head, extra_headers) => { - connector.send_request_extra(head, extra_headers, body.into(), addr) - } - }; - - SendClientRequest::new( - fut, - response_decompress, - timeout.or_else(|| config.timeout), - ) - } - - pub(crate) fn send_json( - mut self, - addr: Option, - response_decompress: bool, - timeout: Option, - config: &ClientConfig, - value: &T, - ) -> SendClientRequest { - let body = match serde_json::to_string(value) { - Ok(body) => body, - Err(e) => return Error::from(e).into(), - }; - - if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") - { - return e.into(); - } - - self.send_body( - addr, - response_decompress, - timeout, - config, - Body::Bytes(Bytes::from(body)), - ) - } - - pub(crate) fn send_form( - mut self, - addr: Option, - response_decompress: bool, - timeout: Option, - config: &ClientConfig, - value: &T, - ) -> SendClientRequest { - let body = match serde_urlencoded::to_string(value) { - Ok(body) => body, - Err(e) => return Error::from(e).into(), - }; - - // set content-type - if let Err(e) = self.set_header_if_none( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) { - return e.into(); - } - - self.send_body( - addr, - response_decompress, - timeout, - config, - Body::Bytes(Bytes::from(body)), - ) - } - - pub(crate) fn send_stream( - self, - addr: Option, - response_decompress: bool, - timeout: Option, - config: &ClientConfig, - stream: S, - ) -> SendClientRequest - where - S: Stream> + Unpin + 'static, - E: Into + 'static, - { - self.send_body( - addr, - response_decompress, - timeout, - config, - Body::from_message(BodyStream::new(stream)), - ) - } - - pub(crate) fn send( - self, - addr: Option, - response_decompress: bool, - timeout: Option, - config: &ClientConfig, - ) -> SendClientRequest { - self.send_body(addr, response_decompress, timeout, config, Body::Empty) - } - - fn set_header_if_none( - &mut self, - key: HeaderName, - value: V, - ) -> Result<(), HttpError> - where - V: IntoHeaderValue, - { - match self { - RequestSender::Owned(head) => { - if !head.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => head.headers.insert(key, value), - Err(e) => return Err(e.into()), - } - } - } - RequestSender::Rc(head, extra_headers) => { - if !head.headers.contains_key(&key) - && !extra_headers.iter().any(|h| h.contains_key(&key)) - { - match value.try_into() { - Ok(v) => { - let h = extra_headers.get_or_insert(HeaderMap::new()); - h.insert(key, v) - } - Err(e) => return Err(e.into()), - }; - } - } - } - - Ok(()) - } -} diff --git a/awc/src/test.rs b/awc/src/test.rs deleted file mode 100644 index a6cbd03e6..000000000 --- a/awc/src/test.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! Test helpers for actix http client to use during testing. -use std::convert::TryFrom; -use std::fmt::Write as FmtWrite; - -use actix_http::cookie::{Cookie, CookieJar, USERINFO}; -use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue}; -use actix_http::http::{Error as HttpError, HeaderName, StatusCode, Version}; -use actix_http::{h1, Payload, ResponseHead}; -use bytes::Bytes; -use percent_encoding::percent_encode; - -use crate::ClientResponse; - -/// Test `ClientResponse` builder -pub struct TestResponse { - head: ResponseHead, - cookies: CookieJar, - payload: Option, -} - -impl Default for TestResponse { - fn default() -> TestResponse { - TestResponse { - head: ResponseHead::new(StatusCode::OK), - cookies: CookieJar::new(), - payload: None, - } - } -} - -impl TestResponse { - /// Create TestResponse and set header - pub fn with_header(key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - Self::default().header(key, value) - } - - /// Set HTTP version of this response - pub fn version(mut self, ver: Version) -> Self { - self.head.version = ver; - self - } - - /// Set a header - pub fn set(mut self, hdr: H) -> Self { - if let Ok(value) = hdr.try_into() { - self.head.headers.append(H::name(), value); - return self; - } - panic!("Can not set header"); - } - - /// Append a header - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - if let Ok(key) = HeaderName::try_from(key) { - if let Ok(value) = value.try_into() { - self.head.headers.append(key, value); - return self; - } - } - panic!("Can not create header"); - } - - /// Set cookie for this response - pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { - self.cookies.add(cookie.into_owned()); - self - } - - /// Set response's payload - pub fn set_payload>(mut self, data: B) -> Self { - let mut payload = h1::Payload::empty(); - payload.unread_data(data.into()); - self.payload = Some(payload.into()); - self - } - - /// Complete response creation and generate `ClientResponse` instance - pub fn finish(self) -> ClientResponse { - let mut head = self.head; - - let mut cookie = String::new(); - for c in self.cookies.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO); - let value = percent_encode(c.value().as_bytes(), USERINFO); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - if !cookie.is_empty() { - head.headers.insert( - header::SET_COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); - } - - if let Some(pl) = self.payload { - ClientResponse::new(head, pl) - } else { - ClientResponse::new(head, h1::Payload::empty().into()) - } - } -} - -#[cfg(test)] -mod tests { - use std::time::SystemTime; - - use super::*; - use crate::{cookie, http::header}; - - #[test] - fn test_basics() { - let res = TestResponse::default() - .version(Version::HTTP_2) - .set(header::Date(SystemTime::now().into())) - .cookie(cookie::Cookie::build("name", "value").finish()) - .finish(); - assert!(res.headers().contains_key(header::SET_COOKIE)); - assert!(res.headers().contains_key(header::DATE)); - assert_eq!(res.version(), Version::HTTP_2); - } -} diff --git a/awc/src/ws.rs b/awc/src/ws.rs deleted file mode 100644 index 89ca50b59..000000000 --- a/awc/src/ws.rs +++ /dev/null @@ -1,499 +0,0 @@ -//! Websockets client -use std::convert::TryFrom; -use std::fmt::Write as FmtWrite; -use std::net::SocketAddr; -use std::rc::Rc; -use std::{fmt, str}; - -use actix_codec::Framed; -use actix_http::cookie::{Cookie, CookieJar}; -use actix_http::{ws, Payload, RequestHead}; -use actix_rt::time::timeout; -use percent_encoding::percent_encode; - -use actix_http::cookie::USERINFO; -pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; - -use crate::connect::BoxedSocket; -use crate::error::{InvalidUrl, SendRequestError, WsClientError}; -use crate::http::header::{ - self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION, -}; -use crate::http::{ - ConnectionType, Error as HttpError, Method, StatusCode, Uri, Version, -}; -use crate::response::ClientResponse; -use crate::ClientConfig; - -/// `WebSocket` connection -pub struct WebsocketsRequest { - pub(crate) head: RequestHead, - err: Option, - origin: Option, - protocols: Option, - addr: Option, - max_size: usize, - server_mode: bool, - cookies: Option, - config: Rc, -} - -impl WebsocketsRequest { - /// Create new websocket connection - pub(crate) fn new(uri: U, config: Rc) -> Self - where - Uri: TryFrom, - >::Error: Into, - { - let mut err = None; - let mut head = RequestHead::default(); - head.method = Method::GET; - head.version = Version::HTTP_11; - - match Uri::try_from(uri) { - Ok(uri) => head.uri = uri, - Err(e) => err = Some(e.into()), - } - - WebsocketsRequest { - head, - err, - config, - addr: None, - origin: None, - protocols: None, - max_size: 65_536, - server_mode: false, - cookies: None, - } - } - - /// Set socket address of the server. - /// - /// This address is used for connection. If address is not - /// provided url's host name get resolved. - pub fn address(mut self, addr: SocketAddr) -> Self { - self.addr = Some(addr); - self - } - - /// Set supported websocket protocols - pub fn protocols(mut self, protos: U) -> Self - where - U: IntoIterator, - V: AsRef, - { - let mut protos = protos - .into_iter() - .fold(String::new(), |acc, s| acc + s.as_ref() + ","); - protos.pop(); - self.protocols = Some(protos); - self - } - - /// Set a cookie - pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { - if self.cookies.is_none() { - let mut jar = CookieJar::new(); - jar.add(cookie.into_owned()); - self.cookies = Some(jar) - } else { - self.cookies.as_mut().unwrap().add(cookie.into_owned()); - } - self - } - - /// Set request Origin - pub fn origin(mut self, origin: V) -> Self - where - HeaderValue: TryFrom, - HttpError: From, - { - match HeaderValue::try_from(origin) { - Ok(value) => self.origin = Some(value), - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Set max frame size - /// - /// By default max size is set to 64kb - pub fn max_frame_size(mut self, size: usize) -> Self { - self.max_size = size; - self - } - - /// Disable payload masking. By default ws client masks frame payload. - pub fn server_mode(mut self) -> Self { - self.server_mode = true; - self - } - - /// Append a header. - /// - /// Header gets appended to existing header. - /// To override header use `set_header()` method. - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - self.head.headers.append(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Insert a header, replaces existing header. - pub fn set_header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - self.head.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Insert a header only if it is not yet set. - pub fn set_header_if_none(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - match HeaderName::try_from(key) { - Ok(key) => { - if !self.head.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => { - self.head.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - } - } - } - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Set HTTP basic authorization header - pub fn basic_auth(self, username: U, password: Option<&str>) -> Self - where - U: fmt::Display, - { - let auth = match password { - Some(password) => format!("{}:{}", username, password), - None => format!("{}:", username), - }; - self.header(AUTHORIZATION, format!("Basic {}", base64::encode(&auth))) - } - - /// Set HTTP bearer authentication header - pub fn bearer_auth(self, token: T) -> Self - where - T: fmt::Display, - { - self.header(AUTHORIZATION, format!("Bearer {}", token)) - } - - /// Complete request construction and connect to a websockets server. - pub async fn connect( - mut self, - ) -> Result<(ClientResponse, Framed), WsClientError> { - if let Some(e) = self.err.take() { - return Err(e.into()); - } - - // validate uri - let uri = &self.head.uri; - if uri.host().is_none() { - return Err(InvalidUrl::MissingHost.into()); - } else if uri.scheme().is_none() { - return Err(InvalidUrl::MissingScheme.into()); - } else if let Some(scheme) = uri.scheme() { - match scheme.as_str() { - "http" | "ws" | "https" | "wss" => (), - _ => return Err(InvalidUrl::UnknownScheme.into()), - } - } else { - return Err(InvalidUrl::UnknownScheme.into()); - } - - if !self.head.headers.contains_key(header::HOST) { - self.head.headers.insert( - header::HOST, - HeaderValue::from_str(uri.host().unwrap()).unwrap(), - ); - } - - // set cookies - if let Some(ref mut jar) = self.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO); - let value = percent_encode(c.value().as_bytes(), USERINFO); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - self.head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); - } - - // origin - if let Some(origin) = self.origin.take() { - self.head.headers.insert(header::ORIGIN, origin); - } - - self.head.set_connection_type(ConnectionType::Upgrade); - self.head - .headers - .insert(header::UPGRADE, HeaderValue::from_static("websocket")); - self.head.headers.insert( - header::SEC_WEBSOCKET_VERSION, - HeaderValue::from_static("13"), - ); - - if let Some(protocols) = self.protocols.take() { - self.head.headers.insert( - header::SEC_WEBSOCKET_PROTOCOL, - HeaderValue::try_from(protocols.as_str()).unwrap(), - ); - } - - // Generate a random key for the `Sec-WebSocket-Key` header. - // a base64-encoded (see Section 4 of [RFC4648]) value that, - // when decoded, is 16 bytes in length (RFC 6455) - let sec_key: [u8; 16] = rand::random(); - let key = base64::encode(&sec_key); - - self.head.headers.insert( - header::SEC_WEBSOCKET_KEY, - HeaderValue::try_from(key.as_str()).unwrap(), - ); - - let head = self.head; - let max_size = self.max_size; - let server_mode = self.server_mode; - - let fut = self - .config - .connector - .borrow_mut() - .open_tunnel(head, self.addr); - - // set request timeout - let (head, framed) = if let Some(to) = self.config.timeout { - timeout(to, fut) - .await - .map_err(|_| SendRequestError::Timeout) - .and_then(|res| res)? - } else { - fut.await? - }; - - // verify response - if head.status != StatusCode::SWITCHING_PROTOCOLS { - return Err(WsClientError::InvalidResponseStatus(head.status)); - } - - // Check for "UPGRADE" to websocket header - let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) { - if let Ok(s) = hdr.to_str() { - s.to_ascii_lowercase().contains("websocket") - } else { - false - } - } else { - false - }; - if !has_hdr { - log::trace!("Invalid upgrade header"); - return Err(WsClientError::InvalidUpgradeHeader); - } - - // Check for "CONNECTION" header - if let Some(conn) = head.headers.get(&header::CONNECTION) { - if let Ok(s) = conn.to_str() { - if !s.to_ascii_lowercase().contains("upgrade") { - log::trace!("Invalid connection header: {}", s); - return Err(WsClientError::InvalidConnectionHeader(conn.clone())); - } - } else { - log::trace!("Invalid connection header: {:?}", conn); - return Err(WsClientError::InvalidConnectionHeader(conn.clone())); - } - } else { - log::trace!("Missing connection header"); - return Err(WsClientError::MissingConnectionHeader); - } - - if let Some(hdr_key) = head.headers.get(&header::SEC_WEBSOCKET_ACCEPT) { - let encoded = ws::hash_key(key.as_ref()); - if hdr_key.as_bytes() != encoded.as_bytes() { - log::trace!( - "Invalid challenge response: expected: {} received: {:?}", - encoded, - key - ); - return Err(WsClientError::InvalidChallengeResponse( - encoded, - hdr_key.clone(), - )); - } - } else { - log::trace!("Missing SEC-WEBSOCKET-ACCEPT header"); - return Err(WsClientError::MissingWebSocketAcceptHeader); - }; - - // response and ws framed - Ok(( - ClientResponse::new(head, Payload::None), - framed.map_codec(|_| { - if server_mode { - ws::Codec::new().max_size(max_size) - } else { - ws::Codec::new().max_size(max_size).client_mode() - } - }), - )) - } -} - -impl fmt::Debug for WebsocketsRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!( - f, - "\nWebsocketsRequest {}:{}", - self.head.method, self.head.uri - )?; - writeln!(f, " headers:")?; - for (key, val) in self.head.headers.iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Client; - - #[actix_rt::test] - async fn test_debug() { - let request = Client::new().ws("/").header("x-test", "111"); - let repr = format!("{:?}", request); - assert!(repr.contains("WebsocketsRequest")); - assert!(repr.contains("x-test")); - } - - #[actix_rt::test] - async fn test_header_override() { - let req = Client::build() - .header(header::CONTENT_TYPE, "111") - .finish() - .ws("/") - .set_header(header::CONTENT_TYPE, "222"); - - assert_eq!( - req.head - .headers - .get(header::CONTENT_TYPE) - .unwrap() - .to_str() - .unwrap(), - "222" - ); - } - - #[actix_rt::test] - async fn basic_auth() { - let req = Client::new() - .ws("/") - .basic_auth("username", Some("password")); - assert_eq!( - req.head - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" - ); - - let req = Client::new().ws("/").basic_auth("username", None); - assert_eq!( - req.head - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Basic dXNlcm5hbWU6" - ); - } - - #[actix_rt::test] - async fn bearer_auth() { - let req = Client::new().ws("/").bearer_auth("someS3cr3tAutht0k3n"); - assert_eq!( - req.head - .headers - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap(), - "Bearer someS3cr3tAutht0k3n" - ); - let _ = req.connect(); - } - - #[actix_rt::test] - async fn basics() { - let req = Client::new() - .ws("http://localhost/") - .origin("test-origin") - .max_frame_size(100) - .server_mode() - .protocols(&["v1", "v2"]) - .set_header_if_none(header::CONTENT_TYPE, "json") - .set_header_if_none(header::CONTENT_TYPE, "text") - .cookie(Cookie::build("cookie1", "value1").finish()); - assert_eq!( - req.origin.as_ref().unwrap().to_str().unwrap(), - "test-origin" - ); - assert_eq!(req.max_size, 100); - assert_eq!(req.server_mode, true); - assert_eq!(req.protocols, Some("v1,v2".to_string())); - assert_eq!( - req.head.headers.get(header::CONTENT_TYPE).unwrap(), - header::HeaderValue::from_static("json") - ); - - let _ = req.connect().await; - - assert!(Client::new().ws("/").connect().await.is_err()); - assert!(Client::new().ws("http:///test").connect().await.is_err()); - assert!(Client::new().ws("hmm://test.com/").connect().await.is_err()); - } -} diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs deleted file mode 100644 index 8fb04b005..000000000 --- a/awc/tests/test_client.rs +++ /dev/null @@ -1,817 +0,0 @@ -use std::collections::HashMap; -use std::io::{Read, Write}; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::time::Duration; - -use brotli2::write::BrotliEncoder; -use bytes::Bytes; -use flate2::read::GzDecoder; -use flate2::write::GzEncoder; -use flate2::Compression; -use futures::future::ok; -use rand::Rng; - -use actix_http::HttpService; -use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory}; -use actix_web::dev::{AppConfig, BodyEncoding}; -use actix_web::http::Cookie; -use actix_web::middleware::Compress; -use actix_web::{ - http::header, test, web, App, Error, HttpMessage, HttpRequest, HttpResponse, -}; -use awc::error::SendRequestError; - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -#[actix_rt::test] -async fn test_simple() { - let srv = test::start(|| { - App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) - }); - - let request = srv.get("/").header("x-test", "111").send(); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - - let mut response = srv.post("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - - // camel case - let response = srv.post("/").camel_case().send().await.unwrap(); - assert!(response.status().is_success()); -} - -#[actix_rt::test] -async fn test_json() { - let srv = test::start(|| { - App::new().service( - web::resource("/").route(web::to(|_: web::Json| HttpResponse::Ok())), - ) - }); - - let request = srv - .get("/") - .header("x-test", "111") - .send_json(&"TEST".to_string()); - let response = request.await.unwrap(); - assert!(response.status().is_success()); -} - -#[actix_rt::test] -async fn test_form() { - let srv = test::start(|| { - App::new().service(web::resource("/").route(web::to( - |_: web::Form>| HttpResponse::Ok(), - ))) - }); - - let mut data = HashMap::new(); - let _ = data.insert("key".to_string(), "TEST".to_string()); - - let request = srv.get("/").header("x-test", "111").send_form(&data); - let response = request.await.unwrap(); - assert!(response.status().is_success()); -} - -#[actix_rt::test] -async fn test_timeout() { - let srv = test::start(|| { - App::new().service(web::resource("/").route(web::to(|| { - async { - actix_rt::time::delay_for(Duration::from_millis(200)).await; - Ok::<_, Error>(HttpResponse::Ok().body(STR)) - } - }))) - }); - - let connector = awc::Connector::new() - .connector(actix_connect::new_connector( - actix_connect::start_default_resolver(), - )) - .timeout(Duration::from_secs(15)) - .finish(); - - let client = awc::Client::build() - .connector(connector) - .timeout(Duration::from_millis(50)) - .finish(); - - let request = client.get(srv.url("/")).send(); - match request.await { - Err(SendRequestError::Timeout) => (), - _ => panic!(), - } -} - -#[actix_rt::test] -async fn test_timeout_override() { - let srv = test::start(|| { - App::new().service(web::resource("/").route(web::to(|| { - async { - actix_rt::time::delay_for(Duration::from_millis(200)).await; - Ok::<_, Error>(HttpResponse::Ok().body(STR)) - } - }))) - }); - - let client = awc::Client::build() - .timeout(Duration::from_millis(50000)) - .finish(); - let request = client - .get(srv.url("/")) - .timeout(Duration::from_millis(50)) - .send(); - match request.await { - Err(SendRequestError::Timeout) => (), - _ => panic!(), - } -} - -#[actix_rt::test] -async fn test_connection_reuse() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let srv = test_server(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then( - HttpService::new(map_config( - App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok()))), - |_| AppConfig::default(), - )) - .tcp(), - ) - }); - - let client = awc::Client::default(); - - // req 1 - let request = client.get(srv.url("/")).send(); - let response = request.await.unwrap(); - assert!(response.status().is_success()); - - // req 2 - let req = client.post(srv.url("/")); - let response = req.send().await.unwrap(); - assert!(response.status().is_success()); - - // one connection - assert_eq!(num.load(Ordering::Relaxed), 1); -} - -#[actix_rt::test] -async fn test_connection_force_close() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let srv = test_server(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then( - HttpService::new(map_config( - App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok()))), - |_| AppConfig::default(), - )) - .tcp(), - ) - }); - - let client = awc::Client::default(); - - // req 1 - let request = client.get(srv.url("/")).force_close().send(); - let response = request.await.unwrap(); - assert!(response.status().is_success()); - - // req 2 - let req = client.post(srv.url("/")).force_close(); - let response = req.send().await.unwrap(); - assert!(response.status().is_success()); - - // two connection - assert_eq!(num.load(Ordering::Relaxed), 2); -} - -#[actix_rt::test] -async fn test_connection_server_close() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let srv = test_server(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then( - HttpService::new(map_config( - App::new().service( - web::resource("/") - .route(web::to(|| HttpResponse::Ok().force_close().finish())), - ), - |_| AppConfig::default(), - )) - .tcp(), - ) - }); - - let client = awc::Client::default(); - - // req 1 - let request = client.get(srv.url("/")).send(); - let response = request.await.unwrap(); - assert!(response.status().is_success()); - - // req 2 - let req = client.post(srv.url("/")); - let response = req.send().await.unwrap(); - assert!(response.status().is_success()); - - // two connection - assert_eq!(num.load(Ordering::Relaxed), 2); -} - -#[actix_rt::test] -async fn test_connection_wait_queue() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let srv = test_server(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then( - HttpService::new(map_config( - App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), - ), - |_| AppConfig::default(), - )) - .tcp(), - ) - }); - - let client = awc::Client::build() - .connector(awc::Connector::new().limit(1).finish()) - .finish(); - - // req 1 - let request = client.get(srv.url("/")).send(); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // req 2 - let req2 = client.post(srv.url("/")); - let req2_fut = req2.send(); - - // read response 1 - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - - // req 2 - let response = req2_fut.await.unwrap(); - assert!(response.status().is_success()); - - // two connection - assert_eq!(num.load(Ordering::Relaxed), 1); -} - -#[actix_rt::test] -async fn test_connection_wait_queue_force_close() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let srv = test_server(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then( - HttpService::new(map_config( - App::new().service( - web::resource("/") - .route(web::to(|| HttpResponse::Ok().force_close().body(STR))), - ), - |_| AppConfig::default(), - )) - .tcp(), - ) - }); - - let client = awc::Client::build() - .connector(awc::Connector::new().limit(1).finish()) - .finish(); - - // req 1 - let request = client.get(srv.url("/")).send(); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // req 2 - let req2 = client.post(srv.url("/")); - let req2_fut = req2.send(); - - // read response 1 - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); - - // req 2 - let response = req2_fut.await.unwrap(); - assert!(response.status().is_success()); - - // two connection - assert_eq!(num.load(Ordering::Relaxed), 2); -} - -#[actix_rt::test] -async fn test_with_query_parameter() { - let srv = test::start(|| { - App::new().service(web::resource("/").to(|req: HttpRequest| { - if req.query_string().contains("qp") { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - })) - }); - - let res = awc::Client::new() - .get(srv.url("/?qp=5")) - .send() - .await - .unwrap(); - assert!(res.status().is_success()); -} - -#[actix_rt::test] -async fn test_no_decompress() { - let srv = test::start(|| { - App::new() - .wrap(Compress::default()) - .service(web::resource("/").route(web::to(|| { - let mut res = HttpResponse::Ok().body(STR); - res.encoding(header::ContentEncoding::Gzip); - res - }))) - }); - - let mut res = awc::Client::new() - .get(srv.url("/")) - .no_decompress() - .send() - .await - .unwrap(); - assert!(res.status().is_success()); - - // read response - let bytes = res.body().await.unwrap(); - - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - - // POST - let mut res = awc::Client::new() - .post(srv.url("/")) - .no_decompress() - .send() - .await - .unwrap(); - assert!(res.status().is_success()); - - let bytes = res.body().await.unwrap(); - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_client_gzip_encoding() { - let srv = test::start(|| { - App::new().service(web::resource("/").route(web::to(|| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let data = e.finish().unwrap(); - - HttpResponse::Ok() - .header("content-encoding", "gzip") - .body(data) - }))) - }); - - // client request - let mut response = srv.post("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_client_gzip_encoding_large() { - let srv = test::start(|| { - App::new().service(web::resource("/").route(web::to(|| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.repeat(10).as_ref()).unwrap(); - let data = e.finish().unwrap(); - - HttpResponse::Ok() - .header("content-encoding", "gzip") - .body(data) - }))) - }); - - // client request - let mut response = srv.post("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from(STR.repeat(10))); -} - -#[actix_rt::test] -async fn test_client_gzip_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(100_000) - .collect::(); - - let srv = test::start(|| { - App::new().service(web::resource("/").route(web::to(|data: Bytes| { - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(&data).unwrap(); - let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "gzip") - .body(data) - }))) - }); - - // client request - let mut response = srv.post("/").send_body(data.clone()).await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[actix_rt::test] -async fn test_client_brotli_encoding() { - let srv = test::start(|| { - App::new().service(web::resource("/").route(web::to(|data: Bytes| { - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(&data).unwrap(); - let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "br") - .body(data) - }))) - }); - - // client request - let mut response = srv.post("/").send_body(STR).await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_client_brotli_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(70_000) - .collect::(); - - let srv = test::start(|| { - App::new().service(web::resource("/").route(web::to(|data: Bytes| { - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(&data).unwrap(); - let data = e.finish().unwrap(); - HttpResponse::Ok() - .header("content-encoding", "br") - .body(data) - }))) - }); - - // client request - let mut response = srv.post("/").send_body(data.clone()).await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -// #[actix_rt::test] -// async fn test_client_deflate_encoding() { -// let srv = test::TestServer::start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Br) -// .body(bytes)) -// }) -// .responder() -// }) -// }); - -// // client request -// let request = srv -// .post() -// .content_encoding(http::ContentEncoding::Deflate) -// .body(STR) -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); - -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } - -// #[actix_rt::test] -// async fn test_client_deflate_encoding_large_random() { -// let data = rand::thread_rng() -// .sample_iter(&rand::distributions::Alphanumeric) -// .take(70_000) -// .collect::(); - -// let srv = test::TestServer::start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Br) -// .body(bytes)) -// }) -// .responder() -// }) -// }); - -// // client request -// let request = srv -// .post() -// .content_encoding(http::ContentEncoding::Deflate) -// .body(data.clone()) -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); - -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from(data)); -// } - -// #[actix_rt::test] -// async fn test_client_streaming_explicit() { -// let srv = test::TestServer::start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .map_err(Error::from) -// .and_then(|body| { -// Ok(HttpResponse::Ok() -// .chunked() -// .content_encoding(http::ContentEncoding::Identity) -// .body(body)) -// }) -// .responder() -// }) -// }); - -// let body = once(Ok(Bytes::from_static(STR.as_ref()))); - -// let request = srv.get("/").body(Body::Streaming(Box::new(body))).unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); - -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } - -// #[actix_rt::test] -// async fn test_body_streaming_implicit() { -// let srv = test::TestServer::start(|app| { -// app.handler(|_| { -// let body = once(Ok(Bytes::from_static(STR.as_ref()))); -// HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Gzip) -// .body(Body::Streaming(Box::new(body))) -// }) -// }); - -// let request = srv.get("/").finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); - -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } - -#[actix_rt::test] -async fn test_client_cookie_handling() { - use std::io::{Error as IoError, ErrorKind}; - - let cookie1 = Cookie::build("cookie1", "value1").finish(); - let cookie2 = Cookie::build("cookie2", "value2") - .domain("www.example.org") - .path("/") - .secure(true) - .http_only(true) - .finish(); - // Q: are all these clones really necessary? A: Yes, possibly - let cookie1b = cookie1.clone(); - let cookie2b = cookie2.clone(); - - let srv = test::start(move || { - let cookie1 = cookie1b.clone(); - let cookie2 = cookie2b.clone(); - - App::new().route( - "/", - web::to(move |req: HttpRequest| { - let cookie1 = cookie1.clone(); - let cookie2 = cookie2.clone(); - - async move { - // Check cookies were sent correctly - let res: Result<(), Error> = req - .cookie("cookie1") - .ok_or(()) - .and_then(|c1| { - if c1.value() == "value1" { - Ok(()) - } else { - Err(()) - } - }) - .and_then(|()| req.cookie("cookie2").ok_or(())) - .and_then(|c2| { - if c2.value() == "value2" { - Ok(()) - } else { - Err(()) - } - }) - .map_err(|_| Error::from(IoError::from(ErrorKind::NotFound))); - - if let Err(e) = res { - Err(e) - } else { - // Send some cookies back - Ok::<_, Error>( - HttpResponse::Ok().cookie(cookie1).cookie(cookie2).finish(), - ) - } - } - }), - ) - }); - - let request = srv.get("/").cookie(cookie1.clone()).cookie(cookie2.clone()); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); - let c1 = response.cookie("cookie1").expect("Missing cookie1"); - assert_eq!(c1, cookie1); - let c2 = response.cookie("cookie2").expect("Missing cookie2"); - assert_eq!(c2, cookie2); -} - -// #[actix_rt::test] -// fn client_read_until_eof() { -// let addr = test::TestServer::unused_addr(); - -// thread::spawn(move || { -// let lst = net::TcpListener::bind(addr).unwrap(); - -// for stream in lst.incoming() { -// let mut stream = stream.unwrap(); -// let mut b = [0; 1000]; -// let _ = stream.read(&mut b).unwrap(); -// let _ = stream -// .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!"); -// } -// }); - -// let mut sys = actix::System::new("test"); - -// // client request -// let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) -// .finish() -// .unwrap(); -// let response = req.send().await.unwrap(); -// assert!(response.status().is_success()); - -// // read response -// let bytes = response.body().await.unwrap(); -// assert_eq!(bytes, Bytes::from_static(b"welcome!")); -// } - -#[actix_rt::test] -async fn client_basic_auth() { - let srv = test::start(|| { - App::new().route( - "/", - web::to(|req: HttpRequest| { - if req - .headers() - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap() - == "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" - { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - ) - }); - - // set authorization header to Basic - let request = srv.get("/").basic_auth("username", Some("password")); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); -} - -#[actix_rt::test] -async fn client_bearer_auth() { - let srv = test::start(|| { - App::new().route( - "/", - web::to(|req: HttpRequest| { - if req - .headers() - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap() - == "Bearer someS3cr3tAutht0k3n" - { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - ) - }); - - // set authorization header to Bearer - let request = srv.get("/").bearer_auth("someS3cr3tAutht0k3n"); - let response = request.send().await.unwrap(); - assert!(response.status().is_success()); -} diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs deleted file mode 100644 index 1d7eb7bc5..000000000 --- a/awc/tests/test_rustls_client.rs +++ /dev/null @@ -1,101 +0,0 @@ -#![cfg(feature = "rustls")] -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; - -use actix_http::HttpService; -use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory, ServiceFactory}; -use actix_web::http::Version; -use actix_web::{dev::AppConfig, web, App, HttpResponse}; -use futures::future::ok; -use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslVerifyMode}; -use rust_tls::ClientConfig; - -fn ssl_acceptor() -> SslAcceptor { - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder.set_verify_callback(SslVerifyMode::NONE, |_, _| true); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(open_ssl::ssl::AlpnError::NOACK) - } - }); - builder.set_alpn_protos(b"\x02h2").unwrap(); - builder.build() -} - -mod danger { - pub struct NoCertificateVerification {} - - impl rust_tls::ServerCertVerifier for NoCertificateVerification { - fn verify_server_cert( - &self, - _roots: &rust_tls::RootCertStore, - _presented_certs: &[rust_tls::Certificate], - _dns_name: webpki::DNSNameRef<'_>, - _ocsp: &[u8], - ) -> Result { - Ok(rust_tls::ServerCertVerified::assertion()) - } - } -} - -// #[actix_rt::test] -async fn _test_connection_reuse_h2() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let srv = test_server(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then( - HttpService::build() - .h2(map_config( - App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok())), - ), - |_| AppConfig::default(), - )) - .openssl(ssl_acceptor()) - .map_err(|_| ()), - ) - }); - - // disable ssl verification - let mut config = ClientConfig::new(); - let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - config.set_protocols(&protos); - config - .dangerous() - .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); - - let client = awc::Client::build() - .connector(awc::Connector::new().rustls(Arc::new(config)).finish()) - .finish(); - - // req 1 - let request = client.get(srv.surl("/")).send(); - let response = request.await.unwrap(); - assert!(response.status().is_success()); - - // req 2 - let req = client.post(srv.surl("/")); - let response = req.send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); - - // one connection - assert_eq!(num.load(Ordering::Relaxed), 1); -} diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs deleted file mode 100644 index d3995b4be..000000000 --- a/awc/tests/test_ssl_client.rs +++ /dev/null @@ -1,82 +0,0 @@ -#![cfg(feature = "openssl")] -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; - -use actix_http::HttpService; -use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory, ServiceFactory}; -use actix_web::http::Version; -use actix_web::{dev::AppConfig, web, App, HttpResponse}; -use futures::future::ok; -use open_ssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; - -fn ssl_acceptor() -> SslAcceptor { - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(open_ssl::ssl::AlpnError::NOACK) - } - }); - builder.set_alpn_protos(b"\x02h2").unwrap(); - builder.build() -} - -#[actix_rt::test] -async fn test_connection_reuse_h2() { - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let srv = test_server(move || { - let num2 = num2.clone(); - pipeline_factory(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - ok(io) - }) - .and_then( - HttpService::build() - .h2(map_config( - App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok())), - ), - |_| AppConfig::default(), - )) - .openssl(ssl_acceptor()) - .map_err(|_| ()), - ) - }); - - // disable ssl verification - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - - let client = awc::Client::build() - .connector(awc::Connector::new().ssl(builder.build()).finish()) - .finish(); - - // req 1 - let request = client.get(srv.surl("/")).send(); - let response = request.await.unwrap(); - assert!(response.status().is_success()); - - // req 2 - let req = client.post(srv.surl("/")); - let response = req.send().await.unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); - - // one connection - assert_eq!(num.load(Ordering::Relaxed), 1); -} diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs deleted file mode 100644 index ee937e43e..000000000 --- a/awc/tests/test_ws.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::io; - -use actix_codec::Framed; -use actix_http::{body::BodySize, h1, ws, Error, HttpService, Request, Response}; -use actix_http_test::test_server; -use bytes::Bytes; -use futures::future::ok; -use futures::{SinkExt, StreamExt}; - -async fn ws_service(req: ws::Frame) -> Result { - match req { - ws::Frame::Ping(msg) => Ok(ws::Message::Pong(msg)), - ws::Frame::Text(text) => Ok(ws::Message::Text( - String::from_utf8(Vec::from(text.as_ref())).unwrap(), - )), - ws::Frame::Binary(bin) => Ok(ws::Message::Binary(bin)), - ws::Frame::Close(reason) => Ok(ws::Message::Close(reason)), - _ => Ok(ws::Message::Close(None)), - } -} - -#[actix_rt::test] -async fn test_simple() { - let mut srv = test_server(|| { - HttpService::build() - .upgrade(|(req, mut framed): (Request, Framed<_, _>)| { - async move { - let res = ws::handshake_response(req.head()).finish(); - // send handshake response - framed - .send(h1::Message::Item((res.drop_body(), BodySize::None))) - .await?; - - // start websocket service - let framed = framed.into_framed(ws::Codec::new()); - ws::Dispatcher::with(framed, ws_service).await - } - }) - .finish(|_| ok::<_, Error>(Response::NotFound())) - .tcp() - }); - - // client service - let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); - - framed - .send(ws::Message::Binary("text".into())) - .await - .unwrap(); - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Binary(Bytes::from_static(b"text"))); - - framed.send(ws::Message::Ping("text".into())).await.unwrap(); - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Pong("text".to_string().into())); - - framed - .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) - .await - .unwrap(); - - let item = framed.next().await.unwrap().unwrap(); - assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); -} diff --git a/benches/server.rs b/benches/server.rs deleted file mode 100644 index 93079a223..000000000 --- a/benches/server.rs +++ /dev/null @@ -1,64 +0,0 @@ -use actix_web::{test, web, App, HttpResponse}; -use awc::Client; -use criterion::{criterion_group, criterion_main, Criterion}; -use futures::future::join_all; - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -// benchmark sending all requests at the same time -fn bench_async_burst(c: &mut Criterion) { - let srv = test::start(|| { - App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) - }); - - // We are using System here, since Runtime requires preinitialized tokio - // Maybe add to actix_rt docs - let url = srv.url("/"); - let mut rt = actix_rt::System::new("test"); - - c.bench_function("get_body_async_burst", move |b| { - b.iter_custom(|iters| { - let client = Client::new().get(url.clone()).freeze().unwrap(); - - let start = std::time::Instant::now(); - // benchmark body - let resps = rt.block_on(async move { - let burst = (0..iters).map(|_| client.send()); - join_all(burst).await - }); - let elapsed = start.elapsed(); - - // if there are failed requests that might be an issue - let failed = resps.iter().filter(|r| r.is_err()).count(); - if failed > 0 { - eprintln!("failed {} requests (might be bench timeout)", failed); - }; - - elapsed - }) - }); -} - -criterion_group!(server_benches, bench_async_burst); -criterion_main!(server_benches); diff --git a/benches/service.rs b/benches/service.rs deleted file mode 100644 index 8adbc8a0c..000000000 --- a/benches/service.rs +++ /dev/null @@ -1,108 +0,0 @@ -use actix_service::Service; -use actix_web::dev::{ServiceRequest, ServiceResponse}; -use actix_web::{web, App, Error, HttpResponse}; -use criterion::{criterion_main, Criterion}; -use std::cell::RefCell; -use std::rc::Rc; - -use actix_web::test::{init_service, ok_service, TestRequest}; - -/// Criterion Benchmark for async Service -/// Should be used from within criterion group: -/// ```rust,ignore -/// let mut criterion: ::criterion::Criterion<_> = -/// ::criterion::Criterion::default().configure_from_args(); -/// bench_async_service(&mut criterion, ok_service(), "async_service_direct"); -/// ``` -/// -/// Usable for benching Service wrappers: -/// Using minimum service code implementation we first measure -/// time to run minimum service, then measure time with wrapper. -/// -/// Sample output -/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us] -pub fn bench_async_service(c: &mut Criterion, srv: S, name: &str) -where - S: Service - + 'static, -{ - let mut rt = actix_rt::System::new("test"); - let srv = Rc::new(RefCell::new(srv)); - - let req = TestRequest::default().to_srv_request(); - assert!(rt - .block_on(srv.borrow_mut().call(req)) - .unwrap() - .status() - .is_success()); - - // start benchmark loops - c.bench_function(name, move |b| { - b.iter_custom(|iters| { - let srv = srv.clone(); - // exclude request generation, it appears it takes significant time vs call (3us vs 1us) - let reqs: Vec<_> = (0..iters) - .map(|_| TestRequest::default().to_srv_request()) - .collect(); - let start = std::time::Instant::now(); - // benchmark body - rt.block_on(async move { - for req in reqs { - srv.borrow_mut().call(req).await.unwrap(); - } - }); - let elapsed = start.elapsed(); - // check that at least first request succeeded - elapsed - }) - }); -} - -async fn index(req: ServiceRequest) -> Result { - Ok(req.into_response(HttpResponse::Ok().finish())) -} - -// Benchmark basic WebService directly -// this approach is usable for benching WebService, though it adds some time to direct service call: -// Sample results on MacBook Pro '14 -// time: [2.0724 us 2.1345 us 2.2074 us] -fn async_web_service(c: &mut Criterion) { - let mut rt = actix_rt::System::new("test"); - let srv = Rc::new(RefCell::new(rt.block_on(init_service( - App::new().service(web::service("/").finish(index)), - )))); - - let req = TestRequest::get().uri("/").to_request(); - assert!(rt - .block_on(srv.borrow_mut().call(req)) - .unwrap() - .status() - .is_success()); - - // start benchmark loops - c.bench_function("async_web_service_direct", move |b| { - b.iter_custom(|iters| { - let srv = srv.clone(); - let reqs = (0..iters).map(|_| TestRequest::get().uri("/").to_request()); - - let start = std::time::Instant::now(); - // benchmark body - rt.block_on(async move { - for req in reqs { - srv.borrow_mut().call(req).await.unwrap(); - } - }); - let elapsed = start.elapsed(); - // check that at least first request succeeded - elapsed - }) - }); -} - -pub fn service_benches() { - let mut criterion: ::criterion::Criterion<_> = - ::criterion::Criterion::default().configure_from_args(); - bench_async_service(&mut criterion, ok_service(), "async_service_direct"); - async_web_service(&mut criterion); -} -criterion_main!(service_benches); diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 90cdfab47..000000000 --- a/codecov.yml +++ /dev/null @@ -1,5 +0,0 @@ -ignore: # ignore codecoverage on following paths - - "**/tests" - - "test-server" - - "**/benches" - - "**/examples" diff --git a/examples/basic.rs b/examples/basic.rs deleted file mode 100644 index bd6f8146f..000000000 --- a/examples/basic.rs +++ /dev/null @@ -1,47 +0,0 @@ -use actix_web::{get, middleware, web, App, HttpRequest, HttpResponse, HttpServer}; - -#[get("/resource1/{name}/index.html")] -async fn index(req: HttpRequest, name: web::Path) -> String { - println!("REQ: {:?}", req); - format!("Hello: {}!\r\n", name) -} - -async fn index_async(req: HttpRequest) -> &'static str { - println!("REQ: {:?}", req); - "Hello world!\r\n" -} - -#[get("/")] -async fn no_params() -> &'static str { - "Hello world!\r\n" -} - -#[actix_rt::main] -async fn main() -> std::io::Result<()> { - std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); - env_logger::init(); - - HttpServer::new(|| { - App::new() - .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) - .wrap(middleware::Compress::default()) - .wrap(middleware::Logger::default()) - .service(index) - .service(no_params) - .service( - web::resource("/resource2/index.html") - .wrap( - middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), - ) - .default_service( - web::route().to(|| HttpResponse::MethodNotAllowed()), - ) - .route(web::get().to(index_async)), - ) - .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) - }) - .bind("127.0.0.1:8080")? - .workers(1) - .run() - .await -} diff --git a/examples/client.rs b/examples/client.rs deleted file mode 100644 index 874e08e1b..000000000 --- a/examples/client.rs +++ /dev/null @@ -1,25 +0,0 @@ -use actix_http::Error; - -#[actix_rt::main] -async fn main() -> Result<(), Error> { - std::env::set_var("RUST_LOG", "actix_http=trace"); - env_logger::init(); - - let client = awc::Client::new(); - - // Create request builder, configure request and send - let mut response = client - .get("https://www.rust-lang.org/") - .header("User-Agent", "Actix-web") - .send() - .await?; - - // server http response - println!("Response: {:?}", response); - - // read response body - let body = response.body().await?; - println!("Downloaded: {:?} bytes", body.len()); - - Ok(()) -} diff --git a/examples/uds.rs b/examples/uds.rs deleted file mode 100644 index 77f245d99..000000000 --- a/examples/uds.rs +++ /dev/null @@ -1,53 +0,0 @@ -use actix_web::{ - get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, -}; - -#[get("/resource1/{name}/index.html")] -async fn index(req: HttpRequest, name: web::Path) -> String { - println!("REQ: {:?}", req); - format!("Hello: {}!\r\n", name) -} - -async fn index_async(req: HttpRequest) -> Result<&'static str, Error> { - println!("REQ: {:?}", req); - Ok("Hello world!\r\n") -} - -#[get("/")] -async fn no_params() -> &'static str { - "Hello world!\r\n" -} - -#[cfg(unix)] -#[actix_rt::main] -async fn main() -> std::io::Result<()> { - std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); - env_logger::init(); - - HttpServer::new(|| { - App::new() - .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) - .wrap(middleware::Compress::default()) - .wrap(middleware::Logger::default()) - .service(index) - .service(no_params) - .service( - web::resource("/resource2/index.html") - .wrap( - middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), - ) - .default_service( - web::route().to(|| HttpResponse::MethodNotAllowed()), - ) - .route(web::get().to(index_async)), - ) - .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) - }) - .bind_uds("/Users/fafhrd91/uds-test")? - .workers(1) - .run() - .await -} - -#[cfg(not(unix))] -fn main() {} diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 94bd11d51..000000000 --- a/rustfmt.toml +++ /dev/null @@ -1,2 +0,0 @@ -max_width = 89 -reorder_imports = true diff --git a/src/app.rs b/src/app.rs deleted file mode 100644 index a060eb53e..000000000 --- a/src/app.rs +++ /dev/null @@ -1,684 +0,0 @@ -use std::cell::RefCell; -use std::fmt; -use std::future::Future; -use std::marker::PhantomData; -use std::rc::Rc; - -use actix_http::body::{Body, MessageBody}; -use actix_http::Extensions; -use actix_service::boxed::{self, BoxServiceFactory}; -use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, Transform, -}; -use futures::future::{FutureExt, LocalBoxFuture}; - -use crate::app_service::{AppEntry, AppInit, AppRoutingFactory}; -use crate::config::ServiceConfig; -use crate::data::{Data, DataFactory}; -use crate::dev::ResourceDef; -use crate::error::Error; -use crate::resource::Resource; -use crate::route::Route; -use crate::service::{ - AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest, - ServiceResponse, -}; - -type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; -type FnDataFactory = - Box LocalBoxFuture<'static, Result, ()>>>; - -/// Application builder - structure that follows the builder pattern -/// for building application instances. -pub struct App { - endpoint: T, - services: Vec>, - default: Option>, - factory_ref: Rc>>, - data: Vec>, - data_factories: Vec, - external: Vec, - extensions: Extensions, - _t: PhantomData, -} - -impl App { - /// Create application builder. Application can be configured with a builder-like pattern. - pub fn new() -> Self { - let fref = Rc::new(RefCell::new(None)); - App { - endpoint: AppEntry::new(fref.clone()), - data: Vec::new(), - data_factories: Vec::new(), - services: Vec::new(), - default: None, - factory_ref: fref, - external: Vec::new(), - extensions: Extensions::new(), - _t: PhantomData, - } - } -} - -impl App -where - B: MessageBody, - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ - /// Set application data. Application data could be accessed - /// by using `Data` extractor where `T` is data type. - /// - /// **Note**: http server accepts an application factory rather than - /// an application instance. Http server constructs an application - /// instance for each thread, thus application data must be constructed - /// multiple times. If you want to share data between different - /// threads, a shared object should be used, e.g. `Arc`. Internally `Data` type - /// uses `Arc` so data could be created outside of app factory and clones could - /// be stored via `App::app_data()` method. - /// - /// ```rust - /// use std::cell::Cell; - /// use actix_web::{web, App, HttpResponse, Responder}; - /// - /// struct MyData { - /// counter: Cell, - /// } - /// - /// async fn index(data: web::Data) -> impl Responder { - /// data.counter.set(data.counter.get() + 1); - /// HttpResponse::Ok() - /// } - /// - /// let app = App::new() - /// .data(MyData{ counter: Cell::new(0) }) - /// .service( - /// web::resource("/index.html").route( - /// web::get().to(index))); - /// ``` - pub fn data(mut self, data: U) -> Self { - self.data.push(Box::new(Data::new(data))); - self - } - - /// Set application data factory. This function is - /// similar to `.data()` but it accepts data factory. Data object get - /// constructed asynchronously during application initialization. - pub fn data_factory(mut self, data: F) -> Self - where - F: Fn() -> Out + 'static, - Out: Future> + 'static, - D: 'static, - E: std::fmt::Debug, - { - self.data_factories.push(Box::new(move || { - { - let fut = data(); - async move { - match fut.await { - Err(e) => { - log::error!("Can not construct data instance: {:?}", e); - Err(()) - } - Ok(data) => { - let data: Box = Box::new(Data::new(data)); - Ok(data) - } - } - } - } - .boxed_local() - })); - self - } - - /// Set application level arbitrary data item. - /// - /// Application data stored with `App::app_data()` method is available - /// via `HttpRequest::app_data()` method at runtime. - /// - /// This method could be used for storing `Data` as well, in that case - /// data could be accessed by using `Data` extractor. - pub fn app_data(mut self, ext: U) -> Self { - self.extensions.insert(ext); - self - } - - /// Run external configuration as part of the application building - /// process - /// - /// This function is useful for moving parts of configuration to a - /// different module or even library. For example, - /// some of the resource's configuration could be moved to different module. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{web, middleware, App, HttpResponse}; - /// - /// // this function could be located in different module - /// fn config(cfg: &mut web::ServiceConfig) { - /// cfg.service(web::resource("/test") - /// .route(web::get().to(|| HttpResponse::Ok())) - /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) - /// ); - /// } - /// - /// fn main() { - /// let app = App::new() - /// .wrap(middleware::Logger::default()) - /// .configure(config) // <- register resources - /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); - /// } - /// ``` - pub fn configure(mut self, f: F) -> Self - where - F: FnOnce(&mut ServiceConfig), - { - let mut cfg = ServiceConfig::new(); - f(&mut cfg); - self.data.extend(cfg.data); - self.services.extend(cfg.services); - self.external.extend(cfg.external); - self - } - - /// Configure route for a specific path. - /// - /// This is a simplified version of the `App::service()` method. - /// This method can be used multiple times with same path, in that case - /// multiple resources with one route would be registered for same resource path. - /// - /// ```rust - /// use actix_web::{web, App, HttpResponse}; - /// - /// async fn index(data: web::Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new() - /// .route("/test1", web::get().to(index)) - /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); - /// } - /// ``` - pub fn route(self, path: &str, mut route: Route) -> Self { - self.service( - Resource::new(path) - .add_guards(route.take_guards()) - .route(route), - ) - } - - /// Register http service. - /// - /// Http service is any type that implements `HttpServiceFactory` trait. - /// - /// Actix web provides several services implementations: - /// - /// * *Resource* is an entry in resource table which corresponds to requested URL. - /// * *Scope* is a set of resources with common root path. - /// * "StaticFiles" is a service for static files support - pub fn service(mut self, factory: F) -> Self - where - F: HttpServiceFactory + 'static, - { - self.services - .push(Box::new(ServiceFactoryWrapper::new(factory))); - self - } - - /// Default service to be used if no matching resource could be found. - /// - /// It is possible to use services like `Resource`, `Route`. - /// - /// ```rust - /// use actix_web::{web, App, HttpResponse}; - /// - /// async fn index() -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new() - /// .service( - /// web::resource("/index.html").route(web::get().to(index))) - /// .default_service( - /// web::route().to(|| HttpResponse::NotFound())); - /// } - /// ``` - /// - /// It is also possible to use static files as default service. - /// - /// ```rust - /// use actix_web::{web, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new() - /// .service( - /// web::resource("/index.html").to(|| HttpResponse::Ok())) - /// .default_service( - /// web::to(|| HttpResponse::NotFound()) - /// ); - /// } - /// ``` - pub fn default_service(mut self, f: F) -> Self - where - F: IntoServiceFactory, - U: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - > + 'static, - U::InitError: fmt::Debug, - { - // create and configure default resource - self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err( - |e| log::error!("Can not construct default service: {:?}", e), - )))); - - self - } - - /// Register an external resource. - /// - /// External resources are useful for URL generation purposes only - /// and are never considered for matching at request time. Calls to - /// `HttpRequest::url_for()` will work as expected. - /// - /// ```rust - /// use actix_web::{web, App, HttpRequest, HttpResponse, Result}; - /// - /// async fn index(req: HttpRequest) -> Result { - /// let url = req.url_for("youtube", &["asdlkjqme"])?; - /// assert_eq!(url.as_str(), "https://youtube.com/watch/asdlkjqme"); - /// Ok(HttpResponse::Ok().into()) - /// } - /// - /// fn main() { - /// let app = App::new() - /// .service(web::resource("/index.html").route( - /// web::get().to(index))) - /// .external_resource("youtube", "https://youtube.com/watch/{video_id}"); - /// } - /// ``` - pub fn external_resource(mut self, name: N, url: U) -> Self - where - N: AsRef, - U: AsRef, - { - let mut rdef = ResourceDef::new(url.as_ref()); - *rdef.name_mut() = name.as_ref().to_string(); - self.external.push(rdef); - self - } - - /// Registers middleware, in the form of a middleware component (type), - /// that runs during inbound and/or outbound processing in the request - /// lifecycle (request -> response), modifying request/response as - /// necessary, across all requests managed by the *Application*. - /// - /// Use middleware when you need to read or modify *every* request or - /// response in some way. - /// - /// Notice that the keyword for registering middleware is `wrap`. As you - /// register middleware using `wrap` in the App builder, imagine wrapping - /// layers around an inner App. The first middleware layer exposed to a - /// Request is the outermost layer-- the *last* registered in - /// the builder chain. Consequently, the *first* middleware registered - /// in the builder chain is the *last* to execute during request processing. - /// - /// ```rust - /// use actix_service::Service; - /// use actix_web::{middleware, web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; - /// - /// async fn index() -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new() - /// .wrap(middleware::Logger::default()) - /// .route("/index.html", web::get().to(index)); - /// } - /// ``` - pub fn wrap( - self, - mw: M, - ) -> App< - impl ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - B1, - > - where - M: Transform< - T::Service, - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - B1: MessageBody, - { - App { - endpoint: apply(mw, self.endpoint), - data: self.data, - data_factories: self.data_factories, - services: self.services, - default: self.default, - factory_ref: self.factory_ref, - external: self.external, - extensions: self.extensions, - _t: PhantomData, - } - } - - /// Registers middleware, in the form of a closure, that runs during inbound - /// and/or outbound processing in the request lifecycle (request -> response), - /// modifying request/response as necessary, across all requests managed by - /// the *Application*. - /// - /// Use middleware when you need to read or modify *every* request or response in some way. - /// - /// ```rust - /// use actix_service::Service; - /// use actix_web::{web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; - /// - /// async fn index() -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new() - /// .wrap_fn(|req, srv| { - /// let fut = srv.call(req); - /// async { - /// let mut res = fut.await?; - /// res.headers_mut().insert( - /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), - /// ); - /// Ok(res) - /// } - /// }) - /// .route("/index.html", web::get().to(index)); - /// } - /// ``` - pub fn wrap_fn( - self, - mw: F, - ) -> App< - impl ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - B1, - > - where - B1: MessageBody, - F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, - R: Future, Error>>, - { - App { - endpoint: apply_fn_factory(self.endpoint, mw), - data: self.data, - data_factories: self.data_factories, - services: self.services, - default: self.default, - factory_ref: self.factory_ref, - external: self.external, - extensions: self.extensions, - _t: PhantomData, - } - } -} - -impl IntoServiceFactory> for App -where - B: MessageBody, - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ - fn into_factory(self) -> AppInit { - AppInit { - data: Rc::new(self.data), - data_factories: Rc::new(self.data_factories), - endpoint: self.endpoint, - services: Rc::new(RefCell::new(self.services)), - external: RefCell::new(self.external), - default: self.default, - factory_ref: self.factory_ref, - extensions: RefCell::new(Some(self.extensions)), - } - } -} - -#[cfg(test)] -mod tests { - use actix_service::Service; - use bytes::Bytes; - use futures::future::ok; - - use super::*; - use crate::http::{header, HeaderValue, Method, StatusCode}; - use crate::middleware::DefaultHeaders; - use crate::service::ServiceRequest; - use crate::test::{call_service, init_service, read_body, TestRequest}; - use crate::{web, HttpRequest, HttpResponse}; - - #[actix_rt::test] - async fn test_default_resource() { - let mut srv = init_service( - App::new().service(web::resource("/test").to(|| HttpResponse::Ok())), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/blah").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let mut srv = init_service( - App::new() - .service(web::resource("/test").to(|| HttpResponse::Ok())) - .service( - web::resource("/test2") - .default_service(|r: ServiceRequest| { - ok(r.into_response(HttpResponse::Created())) - }) - .route(web::get().to(|| HttpResponse::Ok())), - ) - .default_service(|r: ServiceRequest| { - ok(r.into_response(HttpResponse::MethodNotAllowed())) - }), - ) - .await; - - let req = TestRequest::with_uri("/blah").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let req = TestRequest::with_uri("/test2").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test2") - .method(Method::POST) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - } - - #[actix_rt::test] - async fn test_data_factory() { - let mut srv = - init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let mut srv = - init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } - - #[actix_rt::test] - async fn test_extension() { - let mut srv = init_service(App::new().app_data(10usize).service( - web::resource("/").to(|req: HttpRequest| { - assert_eq!(*req.app_data::().unwrap(), 10); - HttpResponse::Ok() - }), - )) - .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_wrap() { - let mut srv = init_service( - App::new() - .wrap( - DefaultHeaders::new() - .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), - ) - .route("/test", web::get().to(|| HttpResponse::Ok())), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - } - - #[actix_rt::test] - async fn test_router_wrap() { - let mut srv = init_service( - App::new() - .route("/test", web::get().to(|| HttpResponse::Ok())) - .wrap( - DefaultHeaders::new() - .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), - ), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - } - - #[actix_rt::test] - async fn test_wrap_fn() { - let mut srv = init_service( - App::new() - .wrap_fn(|req, srv| { - let fut = srv.call(req); - async move { - let mut res = fut.await?; - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); - Ok(res) - } - }) - .service(web::resource("/test").to(|| HttpResponse::Ok())), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - } - - #[actix_rt::test] - async fn test_router_wrap_fn() { - let mut srv = init_service( - App::new() - .route("/test", web::get().to(|| HttpResponse::Ok())) - .wrap_fn(|req, srv| { - let fut = srv.call(req); - async { - let mut res = fut.await?; - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); - Ok(res) - } - }), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - } - - #[actix_rt::test] - async fn test_external_resource() { - let mut srv = init_service( - App::new() - .external_resource("youtube", "https://youtube.com/watch/{video_id}") - .route( - "/test", - web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body(format!( - "{}", - req.url_for("youtube", &["12345"]).unwrap() - )) - }), - ), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp).await; - assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); - } -} diff --git a/src/app_service.rs b/src/app_service.rs deleted file mode 100644 index ccfefbc68..000000000 --- a/src/app_service.rs +++ /dev/null @@ -1,476 +0,0 @@ -use std::cell::RefCell; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; - -use actix_http::{Extensions, Request, Response}; -use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; -use actix_service::boxed::{self, BoxService, BoxServiceFactory}; -use actix_service::{fn_service, Service, ServiceFactory}; -use futures::future::{ok, FutureExt, LocalBoxFuture}; - -use crate::config::{AppConfig, AppService}; -use crate::data::DataFactory; -use crate::error::Error; -use crate::guard::Guard; -use crate::request::{HttpRequest, HttpRequestPool}; -use crate::rmap::ResourceMap; -use crate::service::{AppServiceFactory, ServiceRequest, ServiceResponse}; - -type Guards = Vec>; -type HttpService = BoxService; -type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; -type BoxResponse = LocalBoxFuture<'static, Result>; -type FnDataFactory = - Box LocalBoxFuture<'static, Result, ()>>>; - -/// Service factory to convert `Request` to a `ServiceRequest`. -/// It also executes data factories. -pub struct AppInit -where - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ - pub(crate) endpoint: T, - pub(crate) extensions: RefCell>, - pub(crate) data: Rc>>, - pub(crate) data_factories: Rc>, - pub(crate) services: Rc>>>, - pub(crate) default: Option>, - pub(crate) factory_ref: Rc>>, - pub(crate) external: RefCell>, -} - -impl ServiceFactory for AppInit -where - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ - type Config = AppConfig; - type Request = Request; - type Response = ServiceResponse; - type Error = T::Error; - type InitError = T::InitError; - type Service = AppInitService; - type Future = AppInitResult; - - fn new_service(&self, config: AppConfig) -> Self::Future { - // update resource default service - let default = self.default.clone().unwrap_or_else(|| { - Rc::new(boxed::factory(fn_service(|req: ServiceRequest| { - ok(req.into_response(Response::NotFound().finish())) - }))) - }); - - // App config - let mut config = AppService::new(config, default.clone(), self.data.clone()); - - // register services - std::mem::replace(&mut *self.services.borrow_mut(), Vec::new()) - .into_iter() - .for_each(|mut srv| srv.register(&mut config)); - - let mut rmap = ResourceMap::new(ResourceDef::new("")); - - let (config, services) = config.into_services(); - - // complete pipeline creation - *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { - default, - services: Rc::new( - services - .into_iter() - .map(|(mut rdef, srv, guards, nested)| { - rmap.add(&mut rdef, nested); - (rdef, srv, RefCell::new(guards)) - }) - .collect(), - ), - }); - - // external resources - for mut rdef in std::mem::replace(&mut *self.external.borrow_mut(), Vec::new()) { - rmap.add(&mut rdef, None); - } - - // complete ResourceMap tree creation - let rmap = Rc::new(rmap); - rmap.finish(rmap.clone()); - - AppInitResult { - endpoint: None, - endpoint_fut: self.endpoint.new_service(()), - data: self.data.clone(), - data_factories: Vec::new(), - data_factories_fut: self.data_factories.iter().map(|f| f()).collect(), - extensions: Some( - self.extensions - .borrow_mut() - .take() - .unwrap_or_else(Extensions::new), - ), - config, - rmap, - _t: PhantomData, - } - } -} - -#[pin_project::pin_project] -pub struct AppInitResult -where - T: ServiceFactory, -{ - endpoint: Option, - #[pin] - endpoint_fut: T::Future, - rmap: Rc, - config: AppConfig, - data: Rc>>, - data_factories: Vec>, - data_factories_fut: Vec, ()>>>, - extensions: Option, - _t: PhantomData, -} - -impl Future for AppInitResult -where - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ - type Output = Result, ()>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - // async data factories - let mut idx = 0; - while idx < this.data_factories_fut.len() { - match Pin::new(&mut this.data_factories_fut[idx]).poll(cx)? { - Poll::Ready(f) => { - this.data_factories.push(f); - let _ = this.data_factories_fut.remove(idx); - } - Poll::Pending => idx += 1, - } - } - - if this.endpoint.is_none() { - if let Poll::Ready(srv) = this.endpoint_fut.poll(cx)? { - *this.endpoint = Some(srv); - } - } - - if this.endpoint.is_some() && this.data_factories_fut.is_empty() { - // create app data container - let mut data = this.extensions.take().unwrap(); - for f in this.data.iter() { - f.create(&mut data); - } - - for f in this.data_factories.iter() { - f.create(&mut data); - } - - Poll::Ready(Ok(AppInitService { - service: this.endpoint.take().unwrap(), - rmap: this.rmap.clone(), - config: this.config.clone(), - data: Rc::new(data), - pool: HttpRequestPool::create(), - })) - } else { - Poll::Pending - } - } -} - -/// Service to convert `Request` to a `ServiceRequest` -pub struct AppInitService -where - T: Service, Error = Error>, -{ - service: T, - rmap: Rc, - config: AppConfig, - data: Rc, - pool: &'static HttpRequestPool, -} - -impl Service for AppInitService -where - T: Service, Error = Error>, -{ - type Request = Request; - type Response = ServiceResponse; - type Error = T::Error; - type Future = T::Future; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } - - fn call(&mut self, req: Request) -> Self::Future { - let (head, payload) = req.into_parts(); - - let req = if let Some(mut req) = self.pool.get_request() { - let inner = Rc::get_mut(&mut req.0).unwrap(); - inner.path.get_mut().update(&head.uri); - inner.path.reset(); - inner.head = head; - inner.payload = payload; - inner.app_data = self.data.clone(); - req - } else { - HttpRequest::new( - Path::new(Url::new(head.uri.clone())), - head, - payload, - self.rmap.clone(), - self.config.clone(), - self.data.clone(), - self.pool, - ) - }; - self.service.call(ServiceRequest::new(req)) - } -} - -impl Drop for AppInitService -where - T: Service, Error = Error>, -{ - fn drop(&mut self) { - self.pool.clear(); - } -} - -pub struct AppRoutingFactory { - services: Rc>)>>, - default: Rc, -} - -impl ServiceFactory for AppRoutingFactory { - type Config = (); - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Service = AppRouting; - type Future = AppRoutingFactoryResponse; - - fn new_service(&self, _: ()) -> Self::Future { - AppRoutingFactoryResponse { - fut: self - .services - .iter() - .map(|(path, service, guards)| { - CreateAppRoutingItem::Future( - Some(path.clone()), - guards.borrow_mut().take(), - service.new_service(()).boxed_local(), - ) - }) - .collect(), - default: None, - default_fut: Some(self.default.new_service(())), - } - } -} - -type HttpServiceFut = LocalBoxFuture<'static, Result>; - -/// Create app service -#[doc(hidden)] -pub struct AppRoutingFactoryResponse { - fut: Vec, - default: Option, - default_fut: Option>>, -} - -enum CreateAppRoutingItem { - Future(Option, Option, HttpServiceFut), - Service(ResourceDef, Option, HttpService), -} - -impl Future for AppRoutingFactoryResponse { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut done = true; - - if let Some(ref mut fut) = self.default_fut { - match Pin::new(fut).poll(cx)? { - Poll::Ready(default) => self.default = Some(default), - Poll::Pending => done = false, - } - } - - // poll http services - for item in &mut self.fut { - let res = match item { - CreateAppRoutingItem::Future( - ref mut path, - ref mut guards, - ref mut fut, - ) => match Pin::new(fut).poll(cx) { - Poll::Ready(Ok(service)) => { - Some((path.take().unwrap(), guards.take(), service)) - } - Poll::Ready(Err(_)) => return Poll::Ready(Err(())), - Poll::Pending => { - done = false; - None - } - }, - CreateAppRoutingItem::Service(_, _, _) => continue, - }; - - if let Some((path, guards, service)) = res { - *item = CreateAppRoutingItem::Service(path, guards, service); - } - } - - if done { - let router = self - .fut - .drain(..) - .fold(Router::build(), |mut router, item| { - match item { - CreateAppRoutingItem::Service(path, guards, service) => { - router.rdef(path, service).2 = guards; - } - CreateAppRoutingItem::Future(_, _, _) => unreachable!(), - } - router - }); - Poll::Ready(Ok(AppRouting { - ready: None, - router: router.finish(), - default: self.default.take(), - })) - } else { - Poll::Pending - } - } -} - -pub struct AppRouting { - router: Router, - ready: Option<(ServiceRequest, ResourceInfo)>, - default: Option, -} - -impl Service for AppRouting { - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Future = BoxResponse; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - if self.ready.is_none() { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - } - - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - let res = self.router.recognize_mut_checked(&mut req, |req, guards| { - if let Some(ref guards) = guards { - for f in guards { - if !f.check(req.head()) { - return false; - } - } - } - true - }); - - if let Some((srv, _info)) = res { - srv.call(req) - } else if let Some(ref mut default) = self.default { - default.call(req) - } else { - let req = req.into_parts().0; - ok(ServiceResponse::new(req, Response::NotFound().finish())).boxed_local() - } - } -} - -/// Wrapper service for routing -pub struct AppEntry { - factory: Rc>>, -} - -impl AppEntry { - pub fn new(factory: Rc>>) -> Self { - AppEntry { factory } - } -} - -impl ServiceFactory for AppEntry { - type Config = (); - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Service = AppRouting; - type Future = AppRoutingFactoryResponse; - - fn new_service(&self, _: ()) -> Self::Future { - self.factory.borrow_mut().as_mut().unwrap().new_service(()) - } -} - -#[cfg(test)] -mod tests { - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::Arc; - - use crate::test::{init_service, TestRequest}; - use crate::{web, App, HttpResponse}; - use actix_service::Service; - - struct DropData(Arc); - - impl Drop for DropData { - fn drop(&mut self) { - self.0.store(true, Ordering::Relaxed); - } - } - - #[actix_rt::test] - async fn test_drop_data() { - let data = Arc::new(AtomicBool::new(false)); - - { - let mut app = init_service( - App::new() - .data(DropData(data.clone())) - .service(web::resource("/test").to(|| HttpResponse::Ok())), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let _ = app.call(req).await.unwrap(); - } - assert!(data.load(Ordering::Relaxed)); - } -} diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 6ce96767d..000000000 --- a/src/config.rs +++ /dev/null @@ -1,350 +0,0 @@ -use std::net::SocketAddr; -use std::rc::Rc; - -use actix_http::Extensions; -use actix_router::ResourceDef; -use actix_service::{boxed, IntoServiceFactory, ServiceFactory}; - -use crate::data::{Data, DataFactory}; -use crate::error::Error; -use crate::guard::Guard; -use crate::resource::Resource; -use crate::rmap::ResourceMap; -use crate::route::Route; -use crate::service::{ - AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest, - ServiceResponse, -}; - -type Guards = Vec>; -type HttpNewService = - boxed::BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; - -/// Application configuration -pub struct AppService { - config: AppConfig, - root: bool, - default: Rc, - services: Vec<( - ResourceDef, - HttpNewService, - Option, - Option>, - )>, - service_data: Rc>>, -} - -impl AppService { - /// Crate server settings instance - pub(crate) fn new( - config: AppConfig, - default: Rc, - service_data: Rc>>, - ) -> Self { - AppService { - config, - default, - service_data, - root: true, - services: Vec::new(), - } - } - - /// Check if root is beeing configured - pub fn is_root(&self) -> bool { - self.root - } - - pub(crate) fn into_services( - self, - ) -> ( - AppConfig, - Vec<( - ResourceDef, - HttpNewService, - Option, - Option>, - )>, - ) { - (self.config, self.services) - } - - pub(crate) fn clone_config(&self) -> Self { - AppService { - config: self.config.clone(), - default: self.default.clone(), - services: Vec::new(), - root: false, - service_data: self.service_data.clone(), - } - } - - /// Service configuration - pub fn config(&self) -> &AppConfig { - &self.config - } - - /// Default resource - pub fn default_service(&self) -> Rc { - self.default.clone() - } - - /// Set global route data - pub fn set_service_data(&self, extensions: &mut Extensions) -> bool { - for f in self.service_data.iter() { - f.create(extensions); - } - !self.service_data.is_empty() - } - - /// Register http service - pub fn register_service( - &mut self, - rdef: ResourceDef, - guards: Option>>, - factory: F, - nested: Option>, - ) where - F: IntoServiceFactory, - S: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - > + 'static, - { - self.services.push(( - rdef, - boxed::factory(factory.into_factory()), - guards, - nested, - )); - } -} - -#[derive(Clone)] -pub struct AppConfig(Rc); - -struct AppConfigInner { - secure: bool, - host: String, - addr: SocketAddr, -} - -impl AppConfig { - pub(crate) fn new(secure: bool, addr: SocketAddr, host: String) -> Self { - AppConfig(Rc::new(AppConfigInner { secure, addr, host })) - } - - /// Server host name. - /// - /// Host name is used by application router as a hostname for url generation. - /// Check [ConnectionInfo](./struct.ConnectionInfo.html#method.host) - /// documentation for more information. - /// - /// By default host name is set to a "localhost" value. - pub fn host(&self) -> &str { - &self.0.host - } - - /// Returns true if connection is secure(https) - pub fn secure(&self) -> bool { - self.0.secure - } - - /// Returns the socket address of the local half of this TCP connection - pub fn local_addr(&self) -> SocketAddr { - self.0.addr - } -} - -impl Default for AppConfig { - fn default() -> Self { - AppConfig::new( - false, - "127.0.0.1:8080".parse().unwrap(), - "localhost:8080".to_owned(), - ) - } -} - -/// Service config is used for external configuration. -/// Part of application configuration could be offloaded -/// to set of external methods. This could help with -/// modularization of big application configuration. -pub struct ServiceConfig { - pub(crate) services: Vec>, - pub(crate) data: Vec>, - pub(crate) external: Vec, -} - -impl ServiceConfig { - pub(crate) fn new() -> Self { - Self { - services: Vec::new(), - data: Vec::new(), - external: Vec::new(), - } - } - - /// Set application data. Application data could be accessed - /// by using `Data` extractor where `T` is data type. - /// - /// This is same as `App::data()` method. - pub fn data(&mut self, data: S) -> &mut Self { - self.data.push(Box::new(Data::new(data))); - self - } - - /// Configure route for a specific path. - /// - /// This is same as `App::route()` method. - pub fn route(&mut self, path: &str, mut route: Route) -> &mut Self { - self.service( - Resource::new(path) - .add_guards(route.take_guards()) - .route(route), - ) - } - - /// Register http service. - /// - /// This is same as `App::service()` method. - pub fn service(&mut self, factory: F) -> &mut Self - where - F: HttpServiceFactory + 'static, - { - self.services - .push(Box::new(ServiceFactoryWrapper::new(factory))); - self - } - - /// Register an external resource. - /// - /// External resources are useful for URL generation purposes only - /// and are never considered for matching at request time. Calls to - /// `HttpRequest::url_for()` will work as expected. - /// - /// This is same as `App::external_service()` method. - pub fn external_resource(&mut self, name: N, url: U) -> &mut Self - where - N: AsRef, - U: AsRef, - { - let mut rdef = ResourceDef::new(url.as_ref()); - *rdef.name_mut() = name.as_ref().to_string(); - self.external.push(rdef); - self - } -} - -#[cfg(test)] -mod tests { - use actix_service::Service; - use bytes::Bytes; - - use super::*; - use crate::http::{Method, StatusCode}; - use crate::test::{call_service, init_service, read_body, TestRequest}; - use crate::{web, App, HttpRequest, HttpResponse}; - - #[actix_rt::test] - async fn test_data() { - let cfg = |cfg: &mut ServiceConfig| { - cfg.data(10usize); - }; - - let mut srv = - init_service(App::new().configure(cfg).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - // #[actix_rt::test] - // async fn test_data_factory() { - // let cfg = |cfg: &mut ServiceConfig| { - // cfg.data_factory(|| { - // sleep(std::time::Duration::from_millis(50)).then(|_| { - // println!("READY"); - // Ok::<_, ()>(10usize) - // }) - // }); - // }; - - // let mut srv = - // init_service(App::new().configure(cfg).service( - // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - // )); - // let req = TestRequest::default().to_request(); - // let resp = srv.call(req).await.unwrap(); - // assert_eq!(resp.status(), StatusCode::OK); - - // let cfg2 = |cfg: &mut ServiceConfig| { - // cfg.data_factory(|| Ok::<_, ()>(10u32)); - // }; - // let mut srv = init_service( - // App::new() - // .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())) - // .configure(cfg2), - // ); - // let req = TestRequest::default().to_request(); - // let resp = srv.call(req).await.unwrap(); - // assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - // } - - #[actix_rt::test] - async fn test_external_resource() { - let mut srv = init_service( - App::new() - .configure(|cfg| { - cfg.external_resource( - "youtube", - "https://youtube.com/watch/{video_id}", - ); - }) - .route( - "/test", - web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body(format!( - "{}", - req.url_for("youtube", &["12345"]).unwrap() - )) - }), - ), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp).await; - assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); - } - - #[actix_rt::test] - async fn test_service() { - let mut srv = init_service(App::new().configure(|cfg| { - cfg.service( - web::resource("/test").route(web::get().to(|| HttpResponse::Created())), - ) - .route("/index.html", web::get().to(|| HttpResponse::Ok())); - })) - .await; - - let req = TestRequest::with_uri("/test") - .method(Method::GET) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::CREATED); - - let req = TestRequest::with_uri("/index.html") - .method(Method::GET) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - } -} diff --git a/src/data.rs b/src/data.rs deleted file mode 100644 index c36418942..000000000 --- a/src/data.rs +++ /dev/null @@ -1,281 +0,0 @@ -use std::ops::Deref; -use std::sync::Arc; - -use actix_http::error::{Error, ErrorInternalServerError}; -use actix_http::Extensions; -use futures::future::{err, ok, Ready}; - -use crate::dev::Payload; -use crate::extract::FromRequest; -use crate::request::HttpRequest; - -/// Application data factory -pub(crate) trait DataFactory { - fn create(&self, extensions: &mut Extensions) -> bool; -} - -/// Application data. -/// -/// Application data is an arbitrary data attached to the app. -/// Application data is available to all routes and could be added -/// during application configuration process -/// with `App::data()` method. -/// -/// Application data could be accessed by using `Data` -/// extractor where `T` is data type. -/// -/// **Note**: http server accepts an application factory rather than -/// an application instance. Http server constructs an application -/// instance for each thread, thus application data must be constructed -/// multiple times. If you want to share data between different -/// threads, a shareable object should be used, e.g. `Send + Sync`. Application -/// data does not need to be `Send` or `Sync`. Internally `Data` type -/// uses `Arc`. if your data implements `Send` + `Sync` traits you can -/// use `web::Data::new()` and avoid double `Arc`. -/// -/// If route data is not set for a handler, using `Data` extractor would -/// cause *Internal Server Error* response. -/// -/// ```rust -/// use std::sync::Mutex; -/// use actix_web::{web, App, HttpResponse, Responder}; -/// -/// struct MyData { -/// counter: usize, -/// } -/// -/// /// Use `Data` extractor to access data in handler. -/// async fn index(data: web::Data>) -> impl Responder { -/// let mut data = data.lock().unwrap(); -/// data.counter += 1; -/// HttpResponse::Ok() -/// } -/// -/// fn main() { -/// let data = web::Data::new(Mutex::new(MyData{ counter: 0 })); -/// -/// let app = App::new() -/// // Store `MyData` in application storage. -/// .app_data(data.clone()) -/// .service( -/// web::resource("/index.html").route( -/// web::get().to(index))); -/// } -/// ``` -#[derive(Debug)] -pub struct Data(Arc); - -impl Data { - /// Create new `Data` instance. - /// - /// Internally `Data` type uses `Arc`. if your data implements - /// `Send` + `Sync` traits you can use `web::Data::new()` and - /// avoid double `Arc`. - pub fn new(state: T) -> Data { - Data(Arc::new(state)) - } - - /// Get reference to inner app data. - pub fn get_ref(&self) -> &T { - self.0.as_ref() - } - - /// Convert to the internal Arc - pub fn into_inner(self) -> Arc { - self.0 - } -} - -impl Deref for Data { - type Target = Arc; - - fn deref(&self) -> &Arc { - &self.0 - } -} - -impl Clone for Data { - fn clone(&self) -> Data { - Data(self.0.clone()) - } -} - -impl FromRequest for Data { - type Config = (); - type Error = Error; - type Future = Ready>; - - #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - if let Some(st) = req.app_data::>() { - ok(st.clone()) - } else { - log::debug!( - "Failed to construct App-level Data extractor. \ - Request path: {:?}", - req.path() - ); - err(ErrorInternalServerError( - "App data is not configured, to configure use App::data()", - )) - } - } -} - -impl DataFactory for Data { - fn create(&self, extensions: &mut Extensions) -> bool { - if !extensions.contains::>() { - extensions.insert(Data(self.0.clone())); - true - } else { - false - } - } -} - -#[cfg(test)] -mod tests { - use actix_service::Service; - use std::sync::atomic::{AtomicUsize, Ordering}; - - use super::*; - use crate::http::StatusCode; - use crate::test::{self, init_service, TestRequest}; - use crate::{web, App, HttpResponse}; - - #[actix_rt::test] - async fn test_data_extractor() { - let mut srv = init_service(App::new().data("TEST".to_string()).service( - web::resource("/").to(|data: web::Data| { - assert_eq!(data.to_lowercase(), "test"); - HttpResponse::Ok() - }), - )) - .await; - - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let mut srv = - init_service(App::new().data(10u32).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } - - #[actix_rt::test] - async fn test_app_data_extractor() { - let mut srv = - init_service(App::new().app_data(Data::new(10usize)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; - - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let mut srv = - init_service(App::new().app_data(Data::new(10u32)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } - - #[actix_rt::test] - async fn test_route_data_extractor() { - let mut srv = - init_service(App::new().service(web::resource("/").data(10usize).route( - web::get().to(|data: web::Data| { - let _ = data.clone(); - HttpResponse::Ok() - }), - ))) - .await; - - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - // different type - let mut srv = init_service( - App::new().service( - web::resource("/") - .data(10u32) - .route(web::get().to(|_: web::Data| HttpResponse::Ok())), - ), - ) - .await; - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - } - - #[actix_rt::test] - async fn test_override_data() { - let mut srv = init_service(App::new().data(1usize).service( - web::resource("/").data(10usize).route(web::get().to( - |data: web::Data| { - assert_eq!(**data, 10); - let _ = data.clone(); - HttpResponse::Ok() - }, - )), - )) - .await; - - let req = TestRequest::default().to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_data_drop() { - struct TestData(Arc); - - impl TestData { - fn new(inner: Arc) -> Self { - let _ = inner.fetch_add(1, Ordering::SeqCst); - Self(inner) - } - } - - impl Clone for TestData { - fn clone(&self) -> Self { - let inner = self.0.clone(); - let _ = inner.fetch_add(1, Ordering::SeqCst); - Self(inner) - } - } - - impl Drop for TestData { - fn drop(&mut self) { - let _ = self.0.fetch_sub(1, Ordering::SeqCst); - } - } - - let num = Arc::new(AtomicUsize::new(0)); - let data = TestData::new(num.clone()); - assert_eq!(num.load(Ordering::SeqCst), 1); - - let srv = test::start(move || { - let data = data.clone(); - - App::new() - .data(data) - .service(web::resource("/").to(|_data: Data| async { "ok" })) - }); - - assert!(srv.get("/").send().await.unwrap().status().is_success()); - srv.stop().await; - - assert_eq!(num.load(Ordering::SeqCst), 0); - } -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 31f6b9c5b..000000000 --- a/src/error.rs +++ /dev/null @@ -1,191 +0,0 @@ -//! Error and Result module -pub use actix_http::error::*; -use derive_more::{Display, From}; -use serde_json::error::Error as JsonError; -use url::ParseError as UrlParseError; - -use crate::http::StatusCode; -use crate::HttpResponse; - -/// Errors which can occur when attempting to generate resource uri. -#[derive(Debug, PartialEq, Display, From)] -pub enum UrlGenerationError { - /// Resource not found - #[display(fmt = "Resource not found")] - ResourceNotFound, - /// Not all path pattern covered - #[display(fmt = "Not all path pattern covered")] - NotEnoughElements, - /// URL parse error - #[display(fmt = "{}", _0)] - ParseError(UrlParseError), -} - -/// `InternalServerError` for `UrlGeneratorError` -impl ResponseError for UrlGenerationError {} - -/// A set of errors that can occur during parsing urlencoded payloads -#[derive(Debug, Display, From)] -pub enum UrlencodedError { - /// Can not decode chunked transfer encoding - #[display(fmt = "Can not decode chunked transfer encoding")] - Chunked, - /// Payload size is bigger than allowed. (default: 256kB) - #[display( - fmt = "Urlencoded payload size is bigger ({} bytes) than allowed (default: {} bytes)", - size, - limit - )] - Overflow { size: usize, limit: usize }, - /// Payload size is now known - #[display(fmt = "Payload size is now known")] - UnknownLength, - /// Content type error - #[display(fmt = "Content type error")] - ContentType, - /// Parse error - #[display(fmt = "Parse error")] - Parse, - /// Payload error - #[display(fmt = "Error that occur during reading payload: {}", _0)] - Payload(PayloadError), -} - -/// Return `BadRequest` for `UrlencodedError` -impl ResponseError for UrlencodedError { - fn status_code(&self) -> StatusCode { - match *self { - UrlencodedError::Overflow { .. } => StatusCode::PAYLOAD_TOO_LARGE, - UrlencodedError::UnknownLength => StatusCode::LENGTH_REQUIRED, - _ => StatusCode::BAD_REQUEST, - } - } -} - -/// A set of errors that can occur during parsing json payloads -#[derive(Debug, Display, From)] -pub enum JsonPayloadError { - /// Payload size is bigger than allowed. (default: 32kB) - #[display(fmt = "Json payload size is bigger than allowed")] - Overflow, - /// Content type error - #[display(fmt = "Content type error")] - ContentType, - /// Deserialize error - #[display(fmt = "Json deserialize error: {}", _0)] - Deserialize(JsonError), - /// Payload error - #[display(fmt = "Error that occur during reading payload: {}", _0)] - Payload(PayloadError), -} - -/// Return `BadRequest` for `JsonPayloadError` -impl ResponseError for JsonPayloadError { - fn error_response(&self) -> HttpResponse { - match *self { - JsonPayloadError::Overflow => { - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) - } - _ => HttpResponse::new(StatusCode::BAD_REQUEST), - } - } -} - -/// A set of errors that can occur during parsing request paths -#[derive(Debug, Display, From)] -pub enum PathError { - /// Deserialize error - #[display(fmt = "Path deserialize error: {}", _0)] - Deserialize(serde::de::value::Error), -} - -/// Return `BadRequest` for `PathError` -impl ResponseError for PathError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - -/// A set of errors that can occur during parsing query strings -#[derive(Debug, Display, From)] -pub enum QueryPayloadError { - /// Deserialize error - #[display(fmt = "Query deserialize error: {}", _0)] - Deserialize(serde::de::value::Error), -} - -/// Return `BadRequest` for `QueryPayloadError` -impl ResponseError for QueryPayloadError { - fn status_code(&self) -> StatusCode { - StatusCode::BAD_REQUEST - } -} - -/// Error type returned when reading body as lines. -#[derive(From, Display, Debug)] -pub enum ReadlinesError { - /// Error when decoding a line. - #[display(fmt = "Encoding error")] - /// Payload size is bigger than allowed. (default: 256kB) - EncodingError, - /// Payload error. - #[display(fmt = "Error that occur during reading payload: {}", _0)] - Payload(PayloadError), - /// Line limit exceeded. - #[display(fmt = "Line limit exceeded")] - LimitOverflow, - /// ContentType error. - #[display(fmt = "Content-type error")] - ContentTypeError(ContentTypeError), -} - -/// Return `BadRequest` for `ReadlinesError` -impl ResponseError for ReadlinesError { - fn status_code(&self) -> StatusCode { - match *self { - ReadlinesError::LimitOverflow => StatusCode::PAYLOAD_TOO_LARGE, - _ => StatusCode::BAD_REQUEST, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_urlencoded_error() { - let resp: HttpResponse = - UrlencodedError::Overflow { size: 0, limit: 0 }.error_response(); - assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); - let resp: HttpResponse = UrlencodedError::UnknownLength.error_response(); - assert_eq!(resp.status(), StatusCode::LENGTH_REQUIRED); - let resp: HttpResponse = UrlencodedError::ContentType.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } - - #[test] - fn test_json_payload_error() { - let resp: HttpResponse = JsonPayloadError::Overflow.error_response(); - assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); - let resp: HttpResponse = JsonPayloadError::ContentType.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } - - #[test] - fn test_query_payload_error() { - let resp: HttpResponse = QueryPayloadError::Deserialize( - serde_urlencoded::from_str::("bad query").unwrap_err(), - ) - .error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } - - #[test] - fn test_readlines_error() { - let resp: HttpResponse = ReadlinesError::LimitOverflow.error_response(); - assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); - let resp: HttpResponse = ReadlinesError::EncodingError.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } -} diff --git a/src/extract.rs b/src/extract.rs deleted file mode 100644 index 5289bd7db..000000000 --- a/src/extract.rs +++ /dev/null @@ -1,388 +0,0 @@ -//! Request extractors -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix_http::error::Error; -use futures::future::{ok, FutureExt, LocalBoxFuture, Ready}; - -use crate::dev::Payload; -use crate::request::HttpRequest; - -/// Trait implemented by types that can be extracted from request. -/// -/// Types that implement this trait can be used with `Route` handlers. -pub trait FromRequest: Sized { - /// The associated error which can be returned. - type Error: Into; - - /// Future that resolves to a Self - type Future: Future>; - - /// Configuration for this extractor - type Config: Default + 'static; - - /// Convert request to a Self - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future; - - /// Convert request to a Self - /// - /// This method uses `Payload::None` as payload stream. - fn extract(req: &HttpRequest) -> Self::Future { - Self::from_request(req, &mut Payload::None) - } - - /// Create and configure config instance. - fn configure(f: F) -> Self::Config - where - F: FnOnce(Self::Config) -> Self::Config, - { - f(Self::Config::default()) - } -} - -/// Optionally extract a field from the request -/// -/// If the FromRequest for T fails, return None rather than returning an error response -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest}; -/// use actix_web::error::ErrorBadRequest; -/// use futures::future::{ok, err, Ready}; -/// use serde_derive::Deserialize; -/// use rand; -/// -/// #[derive(Debug, Deserialize)] -/// struct Thing { -/// name: String -/// } -/// -/// impl FromRequest for Thing { -/// type Error = Error; -/// type Future = Ready>; -/// type Config = (); -/// -/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { -/// if rand::random() { -/// ok(Thing { name: "thingy".into() }) -/// } else { -/// err(ErrorBadRequest("no luck")) -/// } -/// -/// } -/// } -/// -/// /// extract `Thing` from request -/// async fn index(supplied_thing: Option) -> String { -/// match supplied_thing { -/// // Puns not intended -/// Some(thing) => format!("Got something: {:?}", thing), -/// None => format!("No thing!") -/// } -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/users/:first").route( -/// web::post().to(index)) -/// ); -/// } -/// ``` -impl FromRequest for Option -where - T: FromRequest, - T::Future: 'static, -{ - type Config = T::Config; - type Error = Error; - type Future = LocalBoxFuture<'static, Result, Error>>; - - #[inline] - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - T::from_request(req, payload) - .then(|r| match r { - Ok(v) => ok(Some(v)), - Err(e) => { - log::debug!("Error for Option extractor: {}", e.into()); - ok(None) - } - }) - .boxed_local() - } -} - -/// Optionally extract a field from the request or extract the Error if unsuccessful -/// -/// If the `FromRequest` for T fails, inject Err into handler rather than returning an error response -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest}; -/// use actix_web::error::ErrorBadRequest; -/// use futures::future::{ok, err, Ready}; -/// use serde_derive::Deserialize; -/// use rand; -/// -/// #[derive(Debug, Deserialize)] -/// struct Thing { -/// name: String -/// } -/// -/// impl FromRequest for Thing { -/// type Error = Error; -/// type Future = Ready>; -/// type Config = (); -/// -/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { -/// if rand::random() { -/// ok(Thing { name: "thingy".into() }) -/// } else { -/// err(ErrorBadRequest("no luck")) -/// } -/// } -/// } -/// -/// /// extract `Thing` from request -/// async fn index(supplied_thing: Result) -> String { -/// match supplied_thing { -/// Ok(thing) => format!("Got thing: {:?}", thing), -/// Err(e) => format!("Error extracting thing: {}", e) -/// } -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/users/:first").route(web::post().to(index)) -/// ); -/// } -/// ``` -impl FromRequest for Result -where - T: FromRequest + 'static, - T::Error: 'static, - T::Future: 'static, -{ - type Config = T::Config; - type Error = Error; - type Future = LocalBoxFuture<'static, Result, Error>>; - - #[inline] - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - T::from_request(req, payload) - .then(|res| match res { - Ok(v) => ok(Ok(v)), - Err(e) => ok(Err(e)), - }) - .boxed_local() - } -} - -#[doc(hidden)] -impl FromRequest for () { - type Config = (); - type Error = Error; - type Future = Ready>; - - fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { - ok(()) - } -} - -macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { - - // This module is a trick to get around the inability of - // `macro_rules!` macros to make new idents. We want to make - // a new `FutWrapper` struct for each distinct invocation of - // this macro. Ideally, we would name it something like - // `FutWrapper_$fut_type`, but this can't be done in a macro_rules - // macro. - // - // Instead, we put everything in a module named `$fut_type`, thus allowing - // us to use the name `FutWrapper` without worrying about conflicts. - // This macro only exists to generate trait impls for tuples - these - // are inherently global, so users don't have to care about this - // weird trick. - #[allow(non_snake_case)] - mod $fut_type { - - // Bring everything into scope, so we don't need - // redundant imports - use super::*; - - /// A helper struct to allow us to pin-project through - /// to individual fields - #[pin_project::pin_project] - struct FutWrapper<$($T: FromRequest),+>($(#[pin] $T::Future),+); - - /// FromRequest implementation for tuple - #[doc(hidden)] - #[allow(unused_parens)] - impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+) - { - type Error = Error; - type Future = $fut_type<$($T),+>; - type Config = ($($T::Config),+); - - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - $fut_type { - items: <($(Option<$T>,)+)>::default(), - futs: FutWrapper($($T::from_request(req, payload),)+), - } - } - } - - #[doc(hidden)] - #[pin_project::pin_project] - pub struct $fut_type<$($T: FromRequest),+> { - items: ($(Option<$T>,)+), - #[pin] - futs: FutWrapper<$($T,)+>, - } - - impl<$($T: FromRequest),+> Future for $fut_type<$($T),+> - { - type Output = Result<($($T,)+), Error>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.project(); - - let mut ready = true; - $( - if this.items.$n.is_none() { - match this.futs.as_mut().project().$n.poll(cx) { - Poll::Ready(Ok(item)) => { - this.items.$n = Some(item); - } - Poll::Pending => ready = false, - Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())), - } - } - )+ - - if ready { - Poll::Ready(Ok( - ($(this.items.$n.take().unwrap(),)+) - )) - } else { - Poll::Pending - } - } - } - } -}); - -#[rustfmt::skip] -mod m { - use super::*; - -tuple_from_req!(TupleFromRequest1, (0, A)); -tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); -tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); -tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D)); -tuple_from_req!(TupleFromRequest5, (0, A), (1, B), (2, C), (3, D), (4, E)); -tuple_from_req!(TupleFromRequest6, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); -tuple_from_req!(TupleFromRequest7, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); -tuple_from_req!(TupleFromRequest8, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); -tuple_from_req!(TupleFromRequest9, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); -tuple_from_req!(TupleFromRequest10, (0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); -} - -#[cfg(test)] -mod tests { - use actix_http::http::header; - use bytes::Bytes; - use serde_derive::Deserialize; - - use super::*; - use crate::test::TestRequest; - use crate::types::{Form, FormConfig}; - - #[derive(Deserialize, Debug, PartialEq)] - struct Info { - hello: String, - } - - #[actix_rt::test] - async fn test_option() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .data(FormConfig::default().limit(4096)) - .to_http_parts(); - - let r = Option::>::from_request(&req, &mut pl) - .await - .unwrap(); - assert_eq!(r, None); - - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"hello=world")) - .to_http_parts(); - - let r = Option::>::from_request(&req, &mut pl) - .await - .unwrap(); - assert_eq!( - r, - Some(Form(Info { - hello: "world".into() - })) - ); - - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .to_http_parts(); - - let r = Option::>::from_request(&req, &mut pl) - .await - .unwrap(); - assert_eq!(r, None); - } - - #[actix_rt::test] - async fn test_result() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .to_http_parts(); - - let r = Result::, Error>::from_request(&req, &mut pl) - .await - .unwrap() - .unwrap(); - assert_eq!( - r, - Form(Info { - hello: "world".into() - }) - ); - - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .to_http_parts(); - - let r = Result::, Error>::from_request(&req, &mut pl) - .await - .unwrap(); - assert!(r.is_err()); - } -} diff --git a/src/guard.rs b/src/guard.rs deleted file mode 100644 index e6303e9c7..000000000 --- a/src/guard.rs +++ /dev/null @@ -1,498 +0,0 @@ -//! Route match guards. -//! -//! Guards are one of the ways how actix-web router chooses a -//! handler service. In essence it is just a function that accepts a -//! reference to a `RequestHead` instance and returns a boolean. -//! It is possible to add guards to *scopes*, *resources* -//! and *routes*. Actix provide several guards by default, like various -//! http methods, header, etc. To become a guard, type must implement `Guard` -//! trait. Simple functions coulds guards as well. -//! -//! Guards can not modify the request object. But it is possible -//! to store extra attributes on a request by using the `Extensions` container. -//! Extensions containers are available via the `RequestHead::extensions()` method. -//! -//! ```rust -//! use actix_web::{web, http, dev, guard, App, HttpResponse}; -//! -//! fn main() { -//! App::new().service(web::resource("/index.html").route( -//! web::route() -//! .guard(guard::Post()) -//! .guard(guard::fn_guard(|head| head.method == http::Method::GET)) -//! .to(|| HttpResponse::MethodNotAllowed())) -//! ); -//! } -//! ``` -#![allow(non_snake_case)] -use std::convert::TryFrom; - -use actix_http::http::{self, header, uri::Uri}; -use actix_http::RequestHead; - -/// Trait defines resource guards. Guards are used for route selection. -/// -/// Guards can not modify the request object. But it is possible -/// to store extra attributes on a request by using the `Extensions` container. -/// Extensions containers are available via the `RequestHead::extensions()` method. -pub trait Guard { - /// Check if request matches predicate - fn check(&self, request: &RequestHead) -> bool; -} - -/// Create guard object for supplied function. -/// -/// ```rust -/// use actix_web::{guard, web, App, HttpResponse}; -/// -/// fn main() { -/// App::new().service(web::resource("/index.html").route( -/// web::route() -/// .guard( -/// guard::fn_guard( -/// |req| req.headers() -/// .contains_key("content-type"))) -/// .to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } -/// ``` -pub fn fn_guard(f: F) -> impl Guard -where - F: Fn(&RequestHead) -> bool, -{ - FnGuard(f) -} - -struct FnGuard bool>(F); - -impl Guard for FnGuard -where - F: Fn(&RequestHead) -> bool, -{ - fn check(&self, head: &RequestHead) -> bool { - (self.0)(head) - } -} - -impl Guard for F -where - F: Fn(&RequestHead) -> bool, -{ - fn check(&self, head: &RequestHead) -> bool { - (self)(head) - } -} - -/// Return guard that matches if any of supplied guards. -/// -/// ```rust -/// use actix_web::{web, guard, App, HttpResponse}; -/// -/// fn main() { -/// App::new().service(web::resource("/index.html").route( -/// web::route() -/// .guard(guard::Any(guard::Get()).or(guard::Post())) -/// .to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } -/// ``` -pub fn Any(guard: F) -> AnyGuard { - AnyGuard(vec![Box::new(guard)]) -} - -/// Matches if any of supplied guards matche. -pub struct AnyGuard(Vec>); - -impl AnyGuard { - /// Add guard to a list of guards to check - pub fn or(mut self, guard: F) -> Self { - self.0.push(Box::new(guard)); - self - } -} - -impl Guard for AnyGuard { - fn check(&self, req: &RequestHead) -> bool { - for p in &self.0 { - if p.check(req) { - return true; - } - } - false - } -} - -/// Return guard that matches if all of the supplied guards. -/// -/// ```rust -/// use actix_web::{guard, web, App, HttpResponse}; -/// -/// fn main() { -/// App::new().service(web::resource("/index.html").route( -/// web::route() -/// .guard( -/// guard::All(guard::Get()).and(guard::Header("content-type", "text/plain"))) -/// .to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } -/// ``` -pub fn All(guard: F) -> AllGuard { - AllGuard(vec![Box::new(guard)]) -} - -/// Matches if all of supplied guards. -pub struct AllGuard(Vec>); - -impl AllGuard { - /// Add new guard to the list of guards to check - pub fn and(mut self, guard: F) -> Self { - self.0.push(Box::new(guard)); - self - } -} - -impl Guard for AllGuard { - fn check(&self, request: &RequestHead) -> bool { - for p in &self.0 { - if !p.check(request) { - return false; - } - } - true - } -} - -/// Return guard that matches if supplied guard does not match. -pub fn Not(guard: F) -> NotGuard { - NotGuard(Box::new(guard)) -} - -#[doc(hidden)] -pub struct NotGuard(Box); - -impl Guard for NotGuard { - fn check(&self, request: &RequestHead) -> bool { - !self.0.check(request) - } -} - -/// Http method guard -#[doc(hidden)] -pub struct MethodGuard(http::Method); - -impl Guard for MethodGuard { - fn check(&self, request: &RequestHead) -> bool { - request.method == self.0 - } -} - -/// Guard to match *GET* http method -pub fn Get() -> MethodGuard { - MethodGuard(http::Method::GET) -} - -/// Predicate to match *POST* http method -pub fn Post() -> MethodGuard { - MethodGuard(http::Method::POST) -} - -/// Predicate to match *PUT* http method -pub fn Put() -> MethodGuard { - MethodGuard(http::Method::PUT) -} - -/// Predicate to match *DELETE* http method -pub fn Delete() -> MethodGuard { - MethodGuard(http::Method::DELETE) -} - -/// Predicate to match *HEAD* http method -pub fn Head() -> MethodGuard { - MethodGuard(http::Method::HEAD) -} - -/// Predicate to match *OPTIONS* http method -pub fn Options() -> MethodGuard { - MethodGuard(http::Method::OPTIONS) -} - -/// Predicate to match *CONNECT* http method -pub fn Connect() -> MethodGuard { - MethodGuard(http::Method::CONNECT) -} - -/// Predicate to match *PATCH* http method -pub fn Patch() -> MethodGuard { - MethodGuard(http::Method::PATCH) -} - -/// Predicate to match *TRACE* http method -pub fn Trace() -> MethodGuard { - MethodGuard(http::Method::TRACE) -} - -/// Predicate to match specified http method -pub fn Method(method: http::Method) -> MethodGuard { - MethodGuard(method) -} - -/// Return predicate that matches if request contains specified header and -/// value. -pub fn Header(name: &'static str, value: &'static str) -> HeaderGuard { - HeaderGuard( - header::HeaderName::try_from(name).unwrap(), - header::HeaderValue::from_static(value), - ) -} - -#[doc(hidden)] -pub struct HeaderGuard(header::HeaderName, header::HeaderValue); - -impl Guard for HeaderGuard { - fn check(&self, req: &RequestHead) -> bool { - if let Some(val) = req.headers.get(&self.0) { - return val == self.1; - } - false - } -} - -/// Return predicate that matches if request contains specified Host name. -/// -/// ```rust -/// use actix_web::{web, guard::Host, App, HttpResponse}; -/// -/// fn main() { -/// App::new().service( -/// web::resource("/index.html") -/// .guard(Host("www.rust-lang.org")) -/// .to(|| HttpResponse::MethodNotAllowed()) -/// ); -/// } -/// ``` -pub fn Host>(host: H) -> HostGuard { - HostGuard(host.as_ref().to_string(), None) -} - -fn get_host_uri(req: &RequestHead) -> Option { - use core::str::FromStr; - req.headers - .get(header::HOST) - .and_then(|host_value| host_value.to_str().ok()) - .or_else(|| req.uri.host()) - .map(|host: &str| Uri::from_str(host).ok()) - .and_then(|host_success| host_success) -} - -#[doc(hidden)] -pub struct HostGuard(String, Option); - -impl HostGuard { - /// Set request scheme to match - pub fn scheme>(mut self, scheme: H) -> HostGuard { - self.1 = Some(scheme.as_ref().to_string()); - self - } -} - -impl Guard for HostGuard { - fn check(&self, req: &RequestHead) -> bool { - let req_host_uri = if let Some(uri) = get_host_uri(req) { - uri - } else { - return false; - }; - - if let Some(uri_host) = req_host_uri.host() { - if self.0 != uri_host { - return false; - } - } else { - return false; - } - - if let Some(ref scheme) = self.1 { - if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() { - return scheme == req_host_uri_scheme; - } - } - - true - } -} - -#[cfg(test)] -mod tests { - use actix_http::http::{header, Method}; - - use super::*; - use crate::test::TestRequest; - - #[test] - fn test_header() { - let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked") - .to_http_request(); - - let pred = Header("transfer-encoding", "chunked"); - assert!(pred.check(req.head())); - - let pred = Header("transfer-encoding", "other"); - assert!(!pred.check(req.head())); - - let pred = Header("content-type", "other"); - assert!(!pred.check(req.head())); - } - - #[test] - fn test_host() { - let req = TestRequest::default() - .header( - header::HOST, - header::HeaderValue::from_static("www.rust-lang.org"), - ) - .to_http_request(); - - let pred = Host("www.rust-lang.org"); - assert!(pred.check(req.head())); - - let pred = Host("www.rust-lang.org").scheme("https"); - assert!(pred.check(req.head())); - - let pred = Host("blog.rust-lang.org"); - assert!(!pred.check(req.head())); - - let pred = Host("blog.rust-lang.org").scheme("https"); - assert!(!pred.check(req.head())); - - let pred = Host("crates.io"); - assert!(!pred.check(req.head())); - - let pred = Host("localhost"); - assert!(!pred.check(req.head())); - } - - #[test] - fn test_host_scheme() { - let req = TestRequest::default() - .header( - header::HOST, - header::HeaderValue::from_static("https://www.rust-lang.org"), - ) - .to_http_request(); - - let pred = Host("www.rust-lang.org").scheme("https"); - assert!(pred.check(req.head())); - - let pred = Host("www.rust-lang.org"); - assert!(pred.check(req.head())); - - let pred = Host("www.rust-lang.org").scheme("http"); - assert!(!pred.check(req.head())); - - let pred = Host("blog.rust-lang.org"); - assert!(!pred.check(req.head())); - - let pred = Host("blog.rust-lang.org").scheme("https"); - assert!(!pred.check(req.head())); - - let pred = Host("crates.io").scheme("https"); - assert!(!pred.check(req.head())); - - let pred = Host("localhost"); - assert!(!pred.check(req.head())); - } - - #[test] - fn test_host_without_header() { - let req = TestRequest::default() - .uri("www.rust-lang.org") - .to_http_request(); - - let pred = Host("www.rust-lang.org"); - assert!(pred.check(req.head())); - - let pred = Host("www.rust-lang.org").scheme("https"); - assert!(pred.check(req.head())); - - let pred = Host("blog.rust-lang.org"); - assert!(!pred.check(req.head())); - - let pred = Host("blog.rust-lang.org").scheme("https"); - assert!(!pred.check(req.head())); - - let pred = Host("crates.io"); - assert!(!pred.check(req.head())); - - let pred = Host("localhost"); - assert!(!pred.check(req.head())); - } - - #[test] - fn test_methods() { - let req = TestRequest::default().to_http_request(); - let req2 = TestRequest::default() - .method(Method::POST) - .to_http_request(); - - assert!(Get().check(req.head())); - assert!(!Get().check(req2.head())); - assert!(Post().check(req2.head())); - assert!(!Post().check(req.head())); - - let r = TestRequest::default().method(Method::PUT).to_http_request(); - assert!(Put().check(r.head())); - assert!(!Put().check(req.head())); - - let r = TestRequest::default() - .method(Method::DELETE) - .to_http_request(); - assert!(Delete().check(r.head())); - assert!(!Delete().check(req.head())); - - let r = TestRequest::default() - .method(Method::HEAD) - .to_http_request(); - assert!(Head().check(r.head())); - assert!(!Head().check(req.head())); - - let r = TestRequest::default() - .method(Method::OPTIONS) - .to_http_request(); - assert!(Options().check(r.head())); - assert!(!Options().check(req.head())); - - let r = TestRequest::default() - .method(Method::CONNECT) - .to_http_request(); - assert!(Connect().check(r.head())); - assert!(!Connect().check(req.head())); - - let r = TestRequest::default() - .method(Method::PATCH) - .to_http_request(); - assert!(Patch().check(r.head())); - assert!(!Patch().check(req.head())); - - let r = TestRequest::default() - .method(Method::TRACE) - .to_http_request(); - assert!(Trace().check(r.head())); - assert!(!Trace().check(req.head())); - } - - #[test] - fn test_preds() { - let r = TestRequest::default() - .method(Method::TRACE) - .to_http_request(); - - assert!(Not(Get()).check(r.head())); - assert!(!Not(Trace()).check(r.head())); - - assert!(All(Trace()).and(Trace()).check(r.head())); - assert!(!All(Get()).and(Trace()).check(r.head())); - - assert!(Any(Get()).or(Trace()).check(r.head())); - assert!(!Any(Get()).or(Get()).check(r.head())); - } -} diff --git a/src/handler.rs b/src/handler.rs deleted file mode 100644 index 33cd2408d..000000000 --- a/src/handler.rs +++ /dev/null @@ -1,291 +0,0 @@ -use std::convert::Infallible; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix_http::{Error, Response}; -use actix_service::{Service, ServiceFactory}; -use futures::future::{ok, Ready}; -use futures::ready; -use pin_project::pin_project; - -use crate::extract::FromRequest; -use crate::request::HttpRequest; -use crate::responder::Responder; -use crate::service::{ServiceRequest, ServiceResponse}; - -/// Async handler converter factory -pub trait Factory: Clone + 'static -where - R: Future, - O: Responder, -{ - fn call(&self, param: T) -> R; -} - -impl Factory<(), R, O> for F -where - F: Fn() -> R + Clone + 'static, - R: Future, - O: Responder, -{ - fn call(&self, _: ()) -> R { - (self)() - } -} - -#[doc(hidden)] -pub struct Handler -where - F: Factory, - R: Future, - O: Responder, -{ - hnd: F, - _t: PhantomData<(T, R, O)>, -} - -impl Handler -where - F: Factory, - R: Future, - O: Responder, -{ - pub fn new(hnd: F) -> Self { - Handler { - hnd, - _t: PhantomData, - } - } -} - -impl Clone for Handler -where - F: Factory, - R: Future, - O: Responder, -{ - fn clone(&self) -> Self { - Handler { - hnd: self.hnd.clone(), - _t: PhantomData, - } - } -} - -impl Service for Handler -where - F: Factory, - R: Future, - O: Responder, -{ - type Request = (T, HttpRequest); - type Response = ServiceResponse; - type Error = Infallible; - type Future = HandlerServiceResponse; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { - HandlerServiceResponse { - fut: self.hnd.call(param), - fut2: None, - req: Some(req), - } - } -} - -#[doc(hidden)] -#[pin_project] -pub struct HandlerServiceResponse -where - T: Future, - R: Responder, -{ - #[pin] - fut: T, - #[pin] - fut2: Option, - req: Option, -} - -impl Future for HandlerServiceResponse -where - T: Future, - R: Responder, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - if let Some(fut) = this.fut2.as_pin_mut() { - return match fut.poll(cx) { - Poll::Ready(Ok(res)) => { - Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) - } - Poll::Pending => Poll::Pending, - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) - } - }; - } - - match this.fut.poll(cx) { - Poll::Ready(res) => { - let fut = res.respond_to(this.req.as_ref().unwrap()); - self.as_mut().project().fut2.set(Some(fut)); - self.poll(cx) - } - Poll::Pending => Poll::Pending, - } - } -} - -/// Extract arguments from request -pub struct Extract { - service: S, - _t: PhantomData, -} - -impl Extract { - pub fn new(service: S) -> Self { - Extract { - service, - _t: PhantomData, - } - } -} - -impl ServiceFactory for Extract -where - S: Service< - Request = (T, HttpRequest), - Response = ServiceResponse, - Error = Infallible, - > + Clone, -{ - type Config = (); - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = (Error, ServiceRequest); - type InitError = (); - type Service = ExtractService; - type Future = Ready>; - - fn new_service(&self, _: ()) -> Self::Future { - ok(ExtractService { - _t: PhantomData, - service: self.service.clone(), - }) - } -} - -pub struct ExtractService { - service: S, - _t: PhantomData, -} - -impl Service for ExtractService -where - S: Service< - Request = (T, HttpRequest), - Response = ServiceResponse, - Error = Infallible, - > + Clone, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = (Error, ServiceRequest); - type Future = ExtractResponse; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: ServiceRequest) -> Self::Future { - let (req, mut payload) = req.into_parts(); - let fut = T::from_request(&req, &mut payload); - - ExtractResponse { - fut, - req, - fut_s: None, - service: self.service.clone(), - } - } -} - -#[pin_project] -pub struct ExtractResponse { - req: HttpRequest, - service: S, - #[pin] - fut: T::Future, - #[pin] - fut_s: Option, -} - -impl Future for ExtractResponse -where - S: Service< - Request = (T, HttpRequest), - Response = ServiceResponse, - Error = Infallible, - >, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - if let Some(fut) = this.fut_s.as_pin_mut() { - return fut.poll(cx).map_err(|_| panic!()); - } - - match ready!(this.fut.poll(cx)) { - Err(e) => { - let req = ServiceRequest::new(this.req.clone()); - Poll::Ready(Err((e.into(), req))) - } - Ok(item) => { - let fut = Some(this.service.call((item, this.req.clone()))); - self.as_mut().project().fut_s.set(fut); - self.poll(cx) - } - } - } -} - -/// FromRequest trait impl for tuples -macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { - impl Factory<($($T,)+), Res, O> for Func - where Func: Fn($($T,)+) -> Res + Clone + 'static, - Res: Future, - O: Responder, - { - fn call(&self, param: ($($T,)+)) -> Res { - (self)($(param.$n,)+) - } - } -}); - -#[rustfmt::skip] -mod m { - use super::*; - -factory_tuple!((0, A)); -factory_tuple!((0, A), (1, B)); -factory_tuple!((0, A), (1, B), (2, C)); -factory_tuple!((0, A), (1, B), (2, C), (3, D)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); -} diff --git a/src/info.rs b/src/info.rs deleted file mode 100644 index c9a642b36..000000000 --- a/src/info.rs +++ /dev/null @@ -1,235 +0,0 @@ -use std::cell::Ref; - -use crate::dev::{AppConfig, RequestHead}; -use crate::http::header::{self, HeaderName}; - -const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for"; -const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host"; -const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto"; - -/// `HttpRequest` connection information -#[derive(Debug, Clone, Default)] -pub struct ConnectionInfo { - scheme: String, - host: String, - remote: Option, - peer: Option, -} - -impl ConnectionInfo { - /// Create *ConnectionInfo* instance for a request. - pub fn get<'a>(req: &'a RequestHead, cfg: &AppConfig) -> Ref<'a, Self> { - if !req.extensions().contains::() { - req.extensions_mut().insert(ConnectionInfo::new(req, cfg)); - } - Ref::map(req.extensions(), |e| e.get().unwrap()) - } - - #[allow(clippy::cognitive_complexity)] - fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo { - let mut host = None; - let mut scheme = None; - let mut remote = None; - let mut peer = None; - - // load forwarded header - for hdr in req.headers.get_all(&header::FORWARDED) { - if let Ok(val) = hdr.to_str() { - for pair in val.split(';') { - for el in pair.split(',') { - let mut items = el.trim().splitn(2, '='); - if let Some(name) = items.next() { - if let Some(val) = items.next() { - match &name.to_lowercase() as &str { - "for" => { - if remote.is_none() { - remote = Some(val.trim()); - } - } - "proto" => { - if scheme.is_none() { - scheme = Some(val.trim()); - } - } - "host" => { - if host.is_none() { - host = Some(val.trim()); - } - } - _ => (), - } - } - } - } - } - } - } - - // scheme - if scheme.is_none() { - if let Some(h) = req - .headers - .get(&HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap()) - { - if let Ok(h) = h.to_str() { - scheme = h.split(',').next().map(|v| v.trim()); - } - } - if scheme.is_none() { - scheme = req.uri.scheme().map(|a| a.as_str()); - if scheme.is_none() && cfg.secure() { - scheme = Some("https") - } - } - } - - // host - if host.is_none() { - if let Some(h) = req - .headers - .get(&HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap()) - { - if let Ok(h) = h.to_str() { - host = h.split(',').next().map(|v| v.trim()); - } - } - if host.is_none() { - if let Some(h) = req.headers.get(&header::HOST) { - host = h.to_str().ok(); - } - if host.is_none() { - host = req.uri.authority().map(|a| a.as_str()); - if host.is_none() { - host = Some(cfg.host()); - } - } - } - } - - // remote addr - if remote.is_none() { - if let Some(h) = req - .headers - .get(&HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap()) - { - if let Ok(h) = h.to_str() { - remote = h.split(',').next().map(|v| v.trim()); - } - } - if remote.is_none() { - // get peeraddr from socketaddr - peer = req.peer_addr.map(|addr| format!("{}", addr)); - } - } - - ConnectionInfo { - peer, - scheme: scheme.unwrap_or("http").to_owned(), - host: host.unwrap_or("localhost").to_owned(), - remote: remote.map(|s| s.to_owned()), - } - } - - /// Scheme of the request. - /// - /// Scheme is resolved through the following headers, in this order: - /// - /// - Forwarded - /// - X-Forwarded-Proto - /// - Uri - #[inline] - pub fn scheme(&self) -> &str { - &self.scheme - } - - /// Hostname of the request. - /// - /// Hostname is resolved through the following headers, in this order: - /// - /// - Forwarded - /// - X-Forwarded-Host - /// - Host - /// - Uri - /// - Server hostname - pub fn host(&self) -> &str { - &self.host - } - - /// Remote socket addr of client initiated HTTP request. - /// - /// The addr is resolved through the following headers, in this order: - /// - /// - Forwarded - /// - X-Forwarded-For - /// - peer name of opened socket - /// - /// # Security - /// Do not use this function for security purposes, unless you can ensure the Forwarded and - /// X-Forwarded-For headers cannot be spoofed by the client. If you want the client's socket - /// address explicitly, use - /// [`HttpRequest::peer_addr()`](../web/struct.HttpRequest.html#method.peer_addr) instead. - #[inline] - pub fn remote(&self) -> Option<&str> { - if let Some(ref r) = self.remote { - Some(r) - } else if let Some(ref peer) = self.peer { - Some(peer) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test::TestRequest; - - #[test] - fn test_forwarded() { - let req = TestRequest::default().to_http_request(); - let info = req.connection_info(); - assert_eq!(info.scheme(), "http"); - assert_eq!(info.host(), "localhost:8080"); - - let req = TestRequest::default() - .header( - header::FORWARDED, - "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org", - ) - .to_http_request(); - - let info = req.connection_info(); - assert_eq!(info.scheme(), "https"); - assert_eq!(info.host(), "rust-lang.org"); - assert_eq!(info.remote(), Some("192.0.2.60")); - - let req = TestRequest::default() - .header(header::HOST, "rust-lang.org") - .to_http_request(); - - let info = req.connection_info(); - assert_eq!(info.scheme(), "http"); - assert_eq!(info.host(), "rust-lang.org"); - assert_eq!(info.remote(), None); - - let req = TestRequest::default() - .header(X_FORWARDED_FOR, "192.0.2.60") - .to_http_request(); - let info = req.connection_info(); - assert_eq!(info.remote(), Some("192.0.2.60")); - - let req = TestRequest::default() - .header(X_FORWARDED_HOST, "192.0.2.60") - .to_http_request(); - let info = req.connection_info(); - assert_eq!(info.host(), "192.0.2.60"); - assert_eq!(info.remote(), None); - - let req = TestRequest::default() - .header(X_FORWARDED_PROTO, "https") - .to_http_request(); - let info = req.connection_info(); - assert_eq!(info.scheme(), "https"); - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index d51005cfe..000000000 --- a/src/lib.rs +++ /dev/null @@ -1,232 +0,0 @@ -#![deny(rust_2018_idioms, warnings)] -#![allow( - clippy::needless_doctest_main, - clippy::type_complexity, - clippy::borrow_interior_mutable_const -)] -//! Actix web is a small, pragmatic, and extremely fast web framework -//! for Rust. -//! -//! ```rust,no_run -//! use actix_web::{web, App, Responder, HttpServer}; -//! -//! async fn index(info: web::Path<(String, u32)>) -> impl Responder { -//! format!("Hello {}! id:{}", info.0, info.1) -//! } -//! -//! #[actix_rt::main] -//! async fn main() -> std::io::Result<()> { -//! HttpServer::new(|| App::new().service( -//! web::resource("/{name}/{id}/index.html").to(index)) -//! ) -//! .bind("127.0.0.1:8080")? -//! .run() -//! .await -//! } -//! ``` -//! -//! ## Documentation & community resources -//! -//! Besides the API documentation (which you are currently looking -//! at!), several other resources are available: -//! -//! * [User Guide](https://actix.rs/docs/) -//! * [Chat on gitter](https://gitter.im/actix/actix) -//! * [GitHub repository](https://github.com/actix/actix-web) -//! * [Cargo package](https://crates.io/crates/actix-web) -//! -//! To get started navigating the API documentation you may want to -//! consider looking at the following pages: -//! -//! * [App](struct.App.html): This struct represents an actix-web -//! application and is used to configure routes and other common -//! settings. -//! -//! * [HttpServer](struct.HttpServer.html): This struct -//! represents an HTTP server instance and is used to instantiate and -//! configure servers. -//! -//! * [web](web/index.html): This module -//! provides essential helper functions and types for application registration. -//! -//! * [HttpRequest](struct.HttpRequest.html) and -//! [HttpResponse](struct.HttpResponse.html): These structs -//! represent HTTP requests and responses and expose various methods -//! for inspecting, creating and otherwise utilizing them. -//! -//! ## Features -//! -//! * Supported *HTTP/1.x* and *HTTP/2.0* protocols -//! * Streaming and pipelining -//! * Keep-alive and slow requests handling -//! * `WebSockets` server/client -//! * Transparent content compression/decompression (br, gzip, deflate) -//! * Configurable request routing -//! * Multipart streams -//! * SSL support with OpenSSL or `native-tls` -//! * Middlewares (`Logger`, `Session`, `CORS`, `DefaultHeaders`) -//! * Supports [Actix actor framework](https://github.com/actix/actix) -//! * Supported Rust version: 1.39 or later -//! -//! ## Package feature -//! -//! * `client` - enables http client (default enabled) -//! * `compress` - enables content encoding compression support (default enabled) -//! * `openssl` - enables ssl support via `openssl` crate, supports `http/2` -//! * `rustls` - enables ssl support via `rustls` crate, supports `http/2` -//! * `secure-cookies` - enables secure cookies support, includes `ring` crate as -//! dependency -#![allow(clippy::type_complexity, clippy::new_without_default)] - -mod app; -mod app_service; -mod config; -mod data; -pub mod error; -mod extract; -pub mod guard; -mod handler; -mod info; -pub mod middleware; -mod request; -mod resource; -mod responder; -mod rmap; -mod route; -mod scope; -mod server; -mod service; -pub mod test; -mod types; -pub mod web; - -#[doc(hidden)] -pub use actix_web_codegen::*; - -// re-export for convenience -pub use actix_http::Response as HttpResponse; -pub use actix_http::{body, cookie, http, Error, HttpMessage, ResponseError, Result}; - -pub use crate::app::App; -pub use crate::extract::FromRequest; -pub use crate::request::HttpRequest; -pub use crate::resource::Resource; -pub use crate::responder::{Either, Responder}; -pub use crate::route::Route; -pub use crate::scope::Scope; -pub use crate::server::HttpServer; - -pub mod dev { - //! The `actix-web` prelude for library developers - //! - //! The purpose of this module is to alleviate imports of many common actix - //! traits by adding a glob import to the top of actix heavy modules: - //! - //! ``` - //! # #![allow(unused_imports)] - //! use actix_web::dev::*; - //! ``` - - pub use crate::config::{AppConfig, AppService}; - #[doc(hidden)] - pub use crate::handler::Factory; - pub use crate::info::ConnectionInfo; - pub use crate::rmap::ResourceMap; - pub use crate::service::{ - HttpServiceFactory, ServiceRequest, ServiceResponse, WebService, - }; - - pub use crate::types::form::UrlEncoded; - pub use crate::types::json::JsonBody; - pub use crate::types::readlines::Readlines; - - pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody, SizedStream}; - #[cfg(feature = "compress")] - pub use actix_http::encoding::Decoder as Decompress; - pub use actix_http::ResponseBuilder as HttpResponseBuilder; - pub use actix_http::{ - Extensions, Payload, PayloadStream, RequestHead, ResponseHead, - }; - pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; - pub use actix_server::Server; - pub use actix_service::{Service, Transform}; - - pub(crate) fn insert_slash(mut patterns: Vec) -> Vec { - for path in &mut patterns { - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; - } - patterns - } - - use crate::http::header::ContentEncoding; - use actix_http::{Response, ResponseBuilder}; - - struct Enc(ContentEncoding); - - /// Helper trait that allows to set specific encoding for response. - pub trait BodyEncoding { - /// Get content encoding - fn get_encoding(&self) -> Option; - - /// Set content encoding - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self; - } - - impl BodyEncoding for ResponseBuilder { - fn get_encoding(&self) -> Option { - if let Some(ref enc) = self.extensions().get::() { - Some(enc.0) - } else { - None - } - } - - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(Enc(encoding)); - self - } - } - - impl BodyEncoding for Response { - fn get_encoding(&self) -> Option { - if let Some(ref enc) = self.extensions().get::() { - Some(enc.0) - } else { - None - } - } - - fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self { - self.extensions_mut().insert(Enc(encoding)); - self - } - } -} - -pub mod client { - //! An HTTP Client - //! - //! ```rust - //! use actix_web::client::Client; - //! - //! #[actix_rt::main] - //! async fn main() { - //! let mut client = Client::default(); - //! - //! // Create request builder and send request - //! let response = client.get("http://www.rust-lang.org") - //! .header("User-Agent", "Actix-web") - //! .send().await; // <- Send http request - //! - //! println!("Response: {:?}", response); - //! } - //! ``` - pub use awc::error::{ - ConnectError, InvalidUrl, PayloadError, SendRequestError, WsClientError, - }; - pub use awc::{ - test, Client, ClientBuilder, ClientRequest, ClientResponse, Connector, - }; -} diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs deleted file mode 100644 index 70006ab3c..000000000 --- a/src/middleware/compress.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! `Middleware` for compressing response body. -use std::cmp; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::str::FromStr; -use std::task::{Context, Poll}; - -use actix_http::body::MessageBody; -use actix_http::encoding::Encoder; -use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING}; -use actix_http::Error; -use actix_service::{Service, Transform}; -use futures::future::{ok, Ready}; -use pin_project::pin_project; - -use crate::dev::BodyEncoding; -use crate::service::{ServiceRequest, ServiceResponse}; - -#[derive(Debug, Clone)] -/// `Middleware` for compressing response body. -/// -/// Use `BodyEncoding` trait for overriding response compression. -/// To disable compression set encoding to `ContentEncoding::Identity` value. -/// -/// ```rust -/// use actix_web::{web, middleware, App, HttpResponse}; -/// -/// fn main() { -/// let app = App::new() -/// .wrap(middleware::Compress::default()) -/// .service( -/// web::resource("/test") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } -/// ``` -pub struct Compress(ContentEncoding); - -impl Compress { - /// Create new `Compress` middleware with default encoding. - pub fn new(encoding: ContentEncoding) -> Self { - Compress(encoding) - } -} - -impl Default for Compress { - fn default() -> Self { - Compress::new(ContentEncoding::Auto) - } -} - -impl Transform for Compress -where - B: MessageBody, - S: Service, Error = Error>, -{ - type Request = ServiceRequest; - type Response = ServiceResponse>; - type Error = Error; - type InitError = (); - type Transform = CompressMiddleware; - type Future = Ready>; - - fn new_transform(&self, service: S) -> Self::Future { - ok(CompressMiddleware { - service, - encoding: self.0, - }) - } -} - -pub struct CompressMiddleware { - service: S, - encoding: ContentEncoding, -} - -impl Service for CompressMiddleware -where - B: MessageBody, - S: Service, Error = Error>, -{ - type Request = ServiceRequest; - type Response = ServiceResponse>; - type Error = Error; - type Future = CompressResponse; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } - - fn call(&mut self, req: ServiceRequest) -> Self::Future { - // negotiate content-encoding - let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) { - if let Ok(enc) = val.to_str() { - AcceptEncoding::parse(enc, self.encoding) - } else { - ContentEncoding::Identity - } - } else { - ContentEncoding::Identity - }; - - CompressResponse { - encoding, - fut: self.service.call(req), - _t: PhantomData, - } - } -} - -#[doc(hidden)] -#[pin_project] -pub struct CompressResponse -where - S: Service, - B: MessageBody, -{ - #[pin] - fut: S::Future, - encoding: ContentEncoding, - _t: PhantomData, -} - -impl Future for CompressResponse -where - B: MessageBody, - S: Service, Error = Error>, -{ - type Output = Result>, Error>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - match futures::ready!(this.fut.poll(cx)) { - Ok(resp) => { - let enc = if let Some(enc) = resp.response().get_encoding() { - enc - } else { - *this.encoding - }; - - Poll::Ready(Ok( - resp.map_body(move |head, body| Encoder::response(enc, head, body)) - )) - } - Err(e) => Poll::Ready(Err(e)), - } - } -} - -struct AcceptEncoding { - encoding: ContentEncoding, - quality: f64, -} - -impl Eq for AcceptEncoding {} - -impl Ord for AcceptEncoding { - #[allow(clippy::comparison_chain)] - fn cmp(&self, other: &AcceptEncoding) -> cmp::Ordering { - if self.quality > other.quality { - cmp::Ordering::Less - } else if self.quality < other.quality { - cmp::Ordering::Greater - } else { - cmp::Ordering::Equal - } - } -} - -impl PartialOrd for AcceptEncoding { - fn partial_cmp(&self, other: &AcceptEncoding) -> Option { - Some(self.cmp(other)) - } -} - -impl PartialEq for AcceptEncoding { - fn eq(&self, other: &AcceptEncoding) -> bool { - self.quality == other.quality - } -} - -impl AcceptEncoding { - fn new(tag: &str) -> Option { - let parts: Vec<&str> = tag.split(';').collect(); - let encoding = match parts.len() { - 0 => return None, - _ => ContentEncoding::from(parts[0]), - }; - let quality = match parts.len() { - 1 => encoding.quality(), - _ => match f64::from_str(parts[1]) { - Ok(q) => q, - Err(_) => 0.0, - }, - }; - Some(AcceptEncoding { encoding, quality }) - } - - /// Parse a raw Accept-Encoding header value into an ordered list. - pub fn parse(raw: &str, encoding: ContentEncoding) -> ContentEncoding { - let mut encodings: Vec<_> = raw - .replace(' ', "") - .split(',') - .map(|l| AcceptEncoding::new(l)) - .collect(); - encodings.sort(); - - for enc in encodings { - if let Some(enc) = enc { - if encoding == ContentEncoding::Auto { - return enc.encoding; - } else if encoding == enc.encoding { - return encoding; - } - } - } - ContentEncoding::Identity - } -} diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs deleted file mode 100644 index 68d06837e..000000000 --- a/src/middleware/condition.rs +++ /dev/null @@ -1,151 +0,0 @@ -//! `Middleware` for conditionally enables another middleware. -use std::task::{Context, Poll}; - -use actix_service::{Service, Transform}; -use futures::future::{ok, Either, FutureExt, LocalBoxFuture}; - -/// `Middleware` for conditionally enables another middleware. -/// The controled middleware must not change the `Service` interfaces. -/// This means you cannot control such middlewares like `Logger` or `Compress`. -/// -/// ## Usage -/// -/// ```rust -/// use actix_web::middleware::{Condition, NormalizePath}; -/// use actix_web::App; -/// -/// # fn main() { -/// let enable_normalize = std::env::var("NORMALIZE_PATH") == Ok("true".into()); -/// let app = App::new() -/// .wrap(Condition::new(enable_normalize, NormalizePath)); -/// # } -/// ``` -pub struct Condition { - trans: T, - enable: bool, -} - -impl Condition { - pub fn new(enable: bool, trans: T) -> Self { - Self { trans, enable } - } -} - -impl Transform for Condition -where - S: Service + 'static, - T: Transform, - T::Future: 'static, - T::InitError: 'static, - T::Transform: 'static, -{ - type Request = S::Request; - type Response = S::Response; - type Error = S::Error; - type InitError = T::InitError; - type Transform = ConditionMiddleware; - type Future = LocalBoxFuture<'static, Result>; - - fn new_transform(&self, service: S) -> Self::Future { - if self.enable { - let f = self.trans.new_transform(service).map(|res| { - res.map( - ConditionMiddleware::Enable as fn(T::Transform) -> Self::Transform, - ) - }); - Either::Left(f) - } else { - Either::Right(ok(ConditionMiddleware::Disable(service))) - } - .boxed_local() - } -} - -pub enum ConditionMiddleware { - Enable(E), - Disable(D), -} - -impl Service for ConditionMiddleware -where - E: Service, - D: Service, -{ - type Request = E::Request; - type Response = E::Response; - type Error = E::Error; - type Future = Either; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - use ConditionMiddleware::*; - match self { - Enable(service) => service.poll_ready(cx), - Disable(service) => service.poll_ready(cx), - } - } - - fn call(&mut self, req: E::Request) -> Self::Future { - use ConditionMiddleware::*; - match self { - Enable(service) => Either::Left(service.call(req)), - Disable(service) => Either::Right(service.call(req)), - } - } -} - -#[cfg(test)] -mod tests { - use actix_service::IntoService; - - use super::*; - use crate::dev::{ServiceRequest, ServiceResponse}; - use crate::error::Result; - use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; - use crate::middleware::errhandlers::*; - use crate::test::{self, TestRequest}; - use crate::HttpResponse; - - fn render_500(mut res: ServiceResponse) -> Result> { - res.response_mut() - .headers_mut() - .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); - Ok(ErrorHandlerResponse::Response(res)) - } - - #[actix_rt::test] - async fn test_handler_enabled() { - let srv = |req: ServiceRequest| { - ok(req.into_response(HttpResponse::InternalServerError().finish())) - }; - - let mw = - ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - - let mut mw = Condition::new(true, mw) - .new_transform(srv.into_service()) - .await - .unwrap(); - let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - } - - #[actix_rt::test] - async fn test_handler_disabled() { - let srv = |req: ServiceRequest| { - ok(req.into_response(HttpResponse::InternalServerError().finish())) - }; - - let mw = - ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - - let mut mw = Condition::new(false, mw) - .new_transform(srv.into_service()) - .await - .unwrap(); - - let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; - assert_eq!(resp.headers().get(CONTENT_TYPE), None); - } -} diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs deleted file mode 100644 index ba001c77b..000000000 --- a/src/middleware/defaultheaders.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! Middleware for setting default response headers -use std::convert::TryFrom; -use std::rc::Rc; -use std::task::{Context, Poll}; - -use actix_service::{Service, Transform}; -use futures::future::{ok, FutureExt, LocalBoxFuture, Ready}; - -use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; -use crate::http::{Error as HttpError, HeaderMap}; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::Error; - -/// `Middleware` for setting default response headers. -/// -/// This middleware does not set header if response headers already contains it. -/// -/// ```rust -/// use actix_web::{web, http, middleware, App, HttpResponse}; -/// -/// fn main() { -/// let app = App::new() -/// .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) -/// .service( -/// web::resource("/test") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } -/// ``` -#[derive(Clone)] -pub struct DefaultHeaders { - inner: Rc, -} - -struct Inner { - ct: bool, - headers: HeaderMap, -} - -impl Default for DefaultHeaders { - fn default() -> Self { - DefaultHeaders { - inner: Rc::new(Inner { - ct: false, - headers: HeaderMap::new(), - }), - } - } -} - -impl DefaultHeaders { - /// Construct `DefaultHeaders` middleware. - pub fn new() -> DefaultHeaders { - DefaultHeaders::default() - } - - /// Set a header. - #[inline] - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, - { - #[allow(clippy::match_wild_err_arm)] - match HeaderName::try_from(key) { - Ok(key) => match HeaderValue::try_from(value) { - Ok(value) => { - Rc::get_mut(&mut self.inner) - .expect("Multiple copies exist") - .headers - .append(key, value); - } - Err(_) => panic!("Can not create header value"), - }, - Err(_) => panic!("Can not create header name"), - } - self - } - - /// Set *CONTENT-TYPE* header if response does not contain this header. - pub fn content_type(mut self) -> Self { - Rc::get_mut(&mut self.inner) - .expect("Multiple copies exist") - .ct = true; - self - } -} - -impl Transform for DefaultHeaders -where - S: Service, Error = Error>, - S::Future: 'static, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Transform = DefaultHeadersMiddleware; - type Future = Ready>; - - fn new_transform(&self, service: S) -> Self::Future { - ok(DefaultHeadersMiddleware { - service, - inner: self.inner.clone(), - }) - } -} - -pub struct DefaultHeadersMiddleware { - service: S, - inner: Rc, -} - -impl Service for DefaultHeadersMiddleware -where - S: Service, Error = Error>, - S::Future: 'static, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Future = 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 { - let inner = self.inner.clone(); - let fut = self.service.call(req); - - async move { - let mut res = fut.await?; - - // set response headers - for (key, value) in inner.headers.iter() { - if !res.headers().contains_key(key) { - res.headers_mut().insert(key.clone(), value.clone()); - } - } - // default content-type - if inner.ct && !res.headers().contains_key(&CONTENT_TYPE) { - res.headers_mut().insert( - CONTENT_TYPE, - HeaderValue::from_static("application/octet-stream"), - ); - } - Ok(res) - } - .boxed_local() - } -} - -#[cfg(test)] -mod tests { - use actix_service::IntoService; - use futures::future::ok; - - use super::*; - use crate::dev::ServiceRequest; - use crate::http::header::CONTENT_TYPE; - use crate::test::{ok_service, TestRequest}; - use crate::HttpResponse; - - #[actix_rt::test] - async fn test_default_headers() { - let mut mw = DefaultHeaders::new() - .header(CONTENT_TYPE, "0001") - .new_transform(ok_service()) - .await - .unwrap(); - - let req = TestRequest::default().to_srv_request(); - let resp = mw.call(req).await.unwrap(); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - - let req = TestRequest::default().to_srv_request(); - let srv = |req: ServiceRequest| { - ok(req - .into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish())) - }; - let mut mw = DefaultHeaders::new() - .header(CONTENT_TYPE, "0001") - .new_transform(srv.into_service()) - .await - .unwrap(); - let resp = mw.call(req).await.unwrap(); - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); - } - - #[actix_rt::test] - async fn test_content_type() { - let srv = - |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); - let mut mw = DefaultHeaders::new() - .content_type() - .new_transform(srv.into_service()) - .await - .unwrap(); - - let req = TestRequest::default().to_srv_request(); - let resp = mw.call(req).await.unwrap(); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - "application/octet-stream" - ); - } -} diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs deleted file mode 100644 index 71886af0b..000000000 --- a/src/middleware/errhandlers.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! Custom handlers service for responses. -use std::rc::Rc; -use std::task::{Context, Poll}; - -use actix_service::{Service, Transform}; -use futures::future::{ok, FutureExt, LocalBoxFuture, Ready}; -use fxhash::FxHashMap; - -use crate::dev::{ServiceRequest, ServiceResponse}; -use crate::error::{Error, Result}; -use crate::http::StatusCode; - -/// Error handler response -pub enum ErrorHandlerResponse { - /// New http response got generated - Response(ServiceResponse), - /// Result is a future that resolves to a new http response - Future(LocalBoxFuture<'static, Result, Error>>), -} - -type ErrorHandler = dyn Fn(ServiceResponse) -> Result>; - -/// `Middleware` for allowing custom handlers for responses. -/// -/// You can use `ErrorHandlers::handler()` method to register a custom error -/// handler for specific status code. You can modify existing response or -/// create completely new one. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::middleware::errhandlers::{ErrorHandlers, ErrorHandlerResponse}; -/// use actix_web::{web, http, dev, App, HttpRequest, HttpResponse, Result}; -/// -/// fn render_500(mut res: dev::ServiceResponse) -> Result> { -/// res.response_mut() -/// .headers_mut() -/// .insert(http::header::CONTENT_TYPE, http::HeaderValue::from_static("Error")); -/// Ok(ErrorHandlerResponse::Response(res)) -/// } -/// -/// # fn main() { -/// let app = App::new() -/// .wrap( -/// ErrorHandlers::new() -/// .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), -/// ) -/// .service(web::resource("/test") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()) -/// )); -/// # } -/// ``` -pub struct ErrorHandlers { - handlers: Rc>>>, -} - -impl Default for ErrorHandlers { - fn default() -> Self { - ErrorHandlers { - handlers: Rc::new(FxHashMap::default()), - } - } -} - -impl ErrorHandlers { - /// Construct new `ErrorHandlers` instance - pub fn new() -> Self { - ErrorHandlers::default() - } - - /// Register error handler for specified status code - pub fn handler(mut self, status: StatusCode, handler: F) -> Self - where - F: Fn(ServiceResponse) -> Result> + 'static, - { - Rc::get_mut(&mut self.handlers) - .unwrap() - .insert(status, Box::new(handler)); - self - } -} - -impl Transform for ErrorHandlers -where - S: Service, Error = Error>, - S::Future: 'static, - B: 'static, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Transform = ErrorHandlersMiddleware; - type Future = Ready>; - - fn new_transform(&self, service: S) -> Self::Future { - ok(ErrorHandlersMiddleware { - service, - handlers: self.handlers.clone(), - }) - } -} - -#[doc(hidden)] -pub struct ErrorHandlersMiddleware { - service: S, - handlers: Rc>>>, -} - -impl Service for ErrorHandlersMiddleware -where - S: Service, Error = Error>, - S::Future: 'static, - B: 'static, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Future = 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 { - let handlers = self.handlers.clone(); - let fut = self.service.call(req); - - async move { - let res = fut.await?; - - if let Some(handler) = handlers.get(&res.status()) { - match handler(res) { - Ok(ErrorHandlerResponse::Response(res)) => Ok(res), - Ok(ErrorHandlerResponse::Future(fut)) => fut.await, - Err(e) => Err(e), - } - } else { - Ok(res) - } - } - .boxed_local() - } -} - -#[cfg(test)] -mod tests { - use actix_service::IntoService; - use futures::future::ok; - - use super::*; - use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; - use crate::test::{self, TestRequest}; - use crate::HttpResponse; - - fn render_500(mut res: ServiceResponse) -> Result> { - res.response_mut() - .headers_mut() - .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); - Ok(ErrorHandlerResponse::Response(res)) - } - - #[actix_rt::test] - async fn test_handler() { - let srv = |req: ServiceRequest| { - ok(req.into_response(HttpResponse::InternalServerError().finish())) - }; - - let mut mw = ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500) - .new_transform(srv.into_service()) - .await - .unwrap(); - - let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - } - - fn render_500_async( - mut res: ServiceResponse, - ) -> Result> { - res.response_mut() - .headers_mut() - .insert(CONTENT_TYPE, HeaderValue::from_static("0001")); - Ok(ErrorHandlerResponse::Future(ok(res).boxed_local())) - } - - #[actix_rt::test] - async fn test_handler_async() { - let srv = |req: ServiceRequest| { - ok(req.into_response(HttpResponse::InternalServerError().finish())) - }; - - let mut mw = ErrorHandlers::new() - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500_async) - .new_transform(srv.into_service()) - .await - .unwrap(); - - let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; - assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); - } -} diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs deleted file mode 100644 index d692132ce..000000000 --- a/src/middleware/logger.rs +++ /dev/null @@ -1,594 +0,0 @@ -//! Request logging middleware -use std::collections::HashSet; -use std::convert::TryFrom; -use std::env; -use std::fmt::{self, Display, Formatter}; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; - -use actix_service::{Service, Transform}; -use bytes::Bytes; -use futures::future::{ok, Ready}; -use log::debug; -use regex::Regex; -use time::OffsetDateTime; - -use crate::dev::{BodySize, MessageBody, ResponseBody}; -use crate::error::{Error, Result}; -use crate::http::{HeaderName, StatusCode}; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::HttpResponse; - -/// `Middleware` for logging request and response info to the terminal. -/// -/// `Logger` middleware uses standard log crate to log information. You should -/// enable logger for `actix_web` package to see access log. -/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar) -/// -/// ## Usage -/// -/// Create `Logger` middleware with the specified `format`. -/// Default `Logger` could be created with `default` method, it uses the -/// default format: -/// -/// ```ignore -/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T -/// ``` -/// ```rust -/// use actix_web::middleware::Logger; -/// use actix_web::App; -/// -/// fn main() { -/// std::env::set_var("RUST_LOG", "actix_web=info"); -/// env_logger::init(); -/// -/// let app = App::new() -/// .wrap(Logger::default()) -/// .wrap(Logger::new("%a %{User-Agent}i")); -/// } -/// ``` -/// -/// ## Format -/// -/// `%%` The percent sign -/// -/// `%a` Remote IP-address (IP-address of proxy if using reverse proxy) -/// -/// `%t` Time when the request was started to process (in rfc3339 format) -/// -/// `%r` First line of request -/// -/// `%s` Response status code -/// -/// `%b` Size of response in bytes, including HTTP headers -/// -/// `%T` Time taken to serve the request, in seconds with floating fraction in -/// .06f format -/// -/// `%D` Time taken to serve the request, in milliseconds -/// -/// `%U` Request URL -/// -/// `%{FOO}i` request.headers['FOO'] -/// -/// `%{FOO}o` response.headers['FOO'] -/// -/// `%{FOO}e` os.environ['FOO'] -/// -pub struct Logger(Rc); - -struct Inner { - format: Format, - exclude: HashSet, -} - -impl Logger { - /// Create `Logger` middleware with the specified `format`. - pub fn new(format: &str) -> Logger { - Logger(Rc::new(Inner { - format: Format::new(format), - exclude: HashSet::new(), - })) - } - - /// Ignore and do not log access info for specified path. - pub fn exclude>(mut self, path: T) -> Self { - Rc::get_mut(&mut self.0) - .unwrap() - .exclude - .insert(path.into()); - self - } -} - -impl Default for Logger { - /// Create `Logger` middleware with format: - /// - /// ```ignore - /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T - /// ``` - fn default() -> Logger { - Logger(Rc::new(Inner { - format: Format::default(), - exclude: HashSet::new(), - })) - } -} - -impl Transform for Logger -where - S: Service, Error = Error>, - B: MessageBody, -{ - type Request = ServiceRequest; - type Response = ServiceResponse>; - type Error = Error; - type InitError = (); - type Transform = LoggerMiddleware; - type Future = Ready>; - - fn new_transform(&self, service: S) -> Self::Future { - ok(LoggerMiddleware { - service, - inner: self.0.clone(), - }) - } -} - -/// Logger middleware -pub struct LoggerMiddleware { - inner: Rc, - service: S, -} - -impl Service for LoggerMiddleware -where - S: Service, Error = Error>, - B: MessageBody, -{ - type Request = ServiceRequest; - type Response = ServiceResponse>; - type Error = Error; - type Future = LoggerResponse; - - 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.exclude.contains(req.path()) { - LoggerResponse { - fut: self.service.call(req), - format: None, - time: OffsetDateTime::now(), - _t: PhantomData, - } - } else { - let now = OffsetDateTime::now(); - let mut format = self.inner.format.clone(); - - for unit in &mut format.0 { - unit.render_request(now, &req); - } - LoggerResponse { - fut: self.service.call(req), - format: Some(format), - time: now, - _t: PhantomData, - } - } - } -} - -#[doc(hidden)] -#[pin_project::pin_project] -pub struct LoggerResponse -where - B: MessageBody, - S: Service, -{ - #[pin] - fut: S::Future, - time: OffsetDateTime, - format: Option, - _t: PhantomData<(B,)>, -} - -impl Future for LoggerResponse -where - B: MessageBody, - S: Service, Error = Error>, -{ - type Output = Result>, Error>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - let res = match futures::ready!(this.fut.poll(cx)) { - Ok(res) => res, - Err(e) => return Poll::Ready(Err(e)), - }; - - if let Some(error) = res.response().error() { - if res.response().head().status != StatusCode::INTERNAL_SERVER_ERROR { - debug!("Error in response: {:?}", error); - } - } - - if let Some(ref mut format) = this.format { - for unit in &mut format.0 { - unit.render_response(res.response()); - } - } - - let time = *this.time; - let format = this.format.take(); - - Poll::Ready(Ok(res.map_body(move |_, body| { - ResponseBody::Body(StreamLog { - body, - time, - format, - size: 0, - }) - }))) - } -} - -pub struct StreamLog { - body: ResponseBody, - format: Option, - size: usize, - time: OffsetDateTime, -} - -impl Drop for StreamLog { - fn drop(&mut self) { - if let Some(ref format) = self.format { - let render = |fmt: &mut Formatter<'_>| { - for unit in &format.0 { - unit.render(fmt, self.size, self.time)?; - } - Ok(()) - }; - log::info!("{}", FormatDisplay(&render)); - } - } -} - -impl MessageBody for StreamLog { - fn size(&self) -> BodySize { - self.body.size() - } - - fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll>> { - match self.body.poll_next(cx) { - Poll::Ready(Some(Ok(chunk))) => { - self.size += chunk.len(); - Poll::Ready(Some(Ok(chunk))) - } - val => val, - } - } -} - -/// A formatting style for the `Logger`, consisting of multiple -/// `FormatText`s concatenated into one line. -#[derive(Clone)] -#[doc(hidden)] -struct Format(Vec); - -impl Default for Format { - /// Return the default formatting style for the `Logger`: - fn default() -> Format { - Format::new(r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#) - } -} - -impl Format { - /// Create a `Format` from a format string. - /// - /// Returns `None` if the format string syntax is incorrect. - pub fn new(s: &str) -> Format { - log::trace!("Access log format: {}", s); - let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrUsbTD]?)").unwrap(); - - let mut idx = 0; - let mut results = Vec::new(); - for cap in fmt.captures_iter(s) { - let m = cap.get(0).unwrap(); - let pos = m.start(); - if idx != pos { - results.push(FormatText::Str(s[idx..pos].to_owned())); - } - idx = m.end(); - - if let Some(key) = cap.get(2) { - results.push(match cap.get(3).unwrap().as_str() { - "i" => FormatText::RequestHeader( - HeaderName::try_from(key.as_str()).unwrap(), - ), - "o" => FormatText::ResponseHeader( - HeaderName::try_from(key.as_str()).unwrap(), - ), - "e" => FormatText::EnvironHeader(key.as_str().to_owned()), - _ => unreachable!(), - }) - } else { - let m = cap.get(1).unwrap(); - results.push(match m.as_str() { - "%" => FormatText::Percent, - "a" => FormatText::RemoteAddr, - "t" => FormatText::RequestTime, - "r" => FormatText::RequestLine, - "s" => FormatText::ResponseStatus, - "b" => FormatText::ResponseSize, - "U" => FormatText::UrlPath, - "T" => FormatText::Time, - "D" => FormatText::TimeMillis, - _ => FormatText::Str(m.as_str().to_owned()), - }); - } - } - if idx != s.len() { - results.push(FormatText::Str(s[idx..].to_owned())); - } - - Format(results) - } -} - -/// A string of text to be logged. This is either one of the data -/// fields supported by the `Logger`, or a custom `String`. -#[doc(hidden)] -#[derive(Debug, Clone)] -pub enum FormatText { - Str(String), - Percent, - RequestLine, - RequestTime, - ResponseStatus, - ResponseSize, - Time, - TimeMillis, - RemoteAddr, - UrlPath, - RequestHeader(HeaderName), - ResponseHeader(HeaderName), - EnvironHeader(String), -} - -impl FormatText { - fn render( - &self, - fmt: &mut Formatter<'_>, - size: usize, - entry_time: OffsetDateTime, - ) -> Result<(), fmt::Error> { - match *self { - FormatText::Str(ref string) => fmt.write_str(string), - FormatText::Percent => "%".fmt(fmt), - FormatText::ResponseSize => size.fmt(fmt), - FormatText::Time => { - let rt = OffsetDateTime::now() - entry_time; - let rt = rt.as_seconds_f64(); - fmt.write_fmt(format_args!("{:.6}", rt)) - } - FormatText::TimeMillis => { - let rt = OffsetDateTime::now() - entry_time; - let rt = (rt.whole_nanoseconds() as f64) / 1_000_000.0; - fmt.write_fmt(format_args!("{:.6}", rt)) - } - FormatText::EnvironHeader(ref name) => { - if let Ok(val) = env::var(name) { - fmt.write_fmt(format_args!("{}", val)) - } else { - "-".fmt(fmt) - } - } - _ => Ok(()), - } - } - - fn render_response(&mut self, res: &HttpResponse) { - match *self { - FormatText::ResponseStatus => { - *self = FormatText::Str(format!("{}", res.status().as_u16())) - } - FormatText::ResponseHeader(ref name) => { - let s = if let Some(val) = res.headers().get(name) { - if let Ok(s) = val.to_str() { - s - } else { - "-" - } - } else { - "-" - }; - *self = FormatText::Str(s.to_string()) - } - _ => (), - } - } - - fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) { - match *self { - FormatText::RequestLine => { - *self = if req.query_string().is_empty() { - FormatText::Str(format!( - "{} {} {:?}", - req.method(), - req.path(), - req.version() - )) - } else { - FormatText::Str(format!( - "{} {}?{} {:?}", - req.method(), - req.path(), - req.query_string(), - req.version() - )) - }; - } - FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()), - FormatText::RequestTime => { - *self = FormatText::Str(now.format("%Y-%m-%dT%H:%M:%S")) - } - FormatText::RequestHeader(ref name) => { - let s = if let Some(val) = req.headers().get(name) { - if let Ok(s) = val.to_str() { - s - } else { - "-" - } - } else { - "-" - }; - *self = FormatText::Str(s.to_string()); - } - FormatText::RemoteAddr => { - let s = if let Some(remote) = req.connection_info().remote() { - FormatText::Str(remote.to_string()) - } else { - FormatText::Str("-".to_string()) - }; - *self = s; - } - _ => (), - } - } -} - -pub(crate) struct FormatDisplay<'a>( - &'a dyn Fn(&mut Formatter<'_>) -> Result<(), fmt::Error>, -); - -impl<'a> fmt::Display for FormatDisplay<'a> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { - (self.0)(fmt) - } -} - -#[cfg(test)] -mod tests { - use actix_service::{IntoService, Service, Transform}; - use futures::future::ok; - - use super::*; - use crate::http::{header, StatusCode}; - use crate::test::TestRequest; - - #[actix_rt::test] - async fn test_logger() { - let srv = |req: ServiceRequest| { - ok(req.into_response( - HttpResponse::build(StatusCode::OK) - .header("X-Test", "ttt") - .finish(), - )) - }; - let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); - - let mut srv = logger.new_transform(srv.into_service()).await.unwrap(); - - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ) - .to_srv_request(); - let _res = srv.call(req).await; - } - - #[actix_rt::test] - async fn test_url_path() { - let mut format = Format::new("%T %U"); - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ) - .uri("/test/route/yeah") - .to_srv_request(); - - let now = OffsetDateTime::now(); - for unit in &mut format.0 { - unit.render_request(now, &req); - } - - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - for unit in &mut format.0 { - unit.render_response(&resp); - } - - let render = |fmt: &mut Formatter<'_>| { - for unit in &format.0 { - unit.render(fmt, 1024, now)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - println!("{}", s); - assert!(s.contains("/test/route/yeah")); - } - - #[actix_rt::test] - async fn test_default_format() { - let mut format = Format::default(); - - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ) - .to_srv_request(); - - let now = OffsetDateTime::now(); - for unit in &mut format.0 { - unit.render_request(now, &req); - } - - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - for unit in &mut format.0 { - unit.render_response(&resp); - } - - let entry_time = OffsetDateTime::now(); - let render = |fmt: &mut Formatter<'_>| { - for unit in &format.0 { - unit.render(fmt, 1024, entry_time)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains("GET / HTTP/1.1")); - assert!(s.contains("200 1024")); - assert!(s.contains("ACTIX-WEB")); - } - - #[actix_rt::test] - async fn test_request_time_format() { - let mut format = Format::new("%t"); - let req = TestRequest::default().to_srv_request(); - - let now = OffsetDateTime::now(); - for unit in &mut format.0 { - unit.render_request(now, &req); - } - - let resp = HttpResponse::build(StatusCode::OK).force_close().finish(); - for unit in &mut format.0 { - unit.render_response(&resp); - } - - let render = |fmt: &mut Formatter<'_>| { - for unit in &format.0 { - unit.render(fmt, 1024, now)?; - } - Ok(()) - }; - let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains(&format!("{}", now.format("%Y-%m-%dT%H:%M:%S")))); - } -} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs deleted file mode 100644 index f0d42cc2a..000000000 --- a/src/middleware/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Middlewares - -#[cfg(feature = "compress")] -mod compress; -#[cfg(feature = "compress")] -pub use self::compress::Compress; - -mod condition; -mod defaultheaders; -pub mod errhandlers; -mod logger; -mod normalize; - -pub use self::condition::Condition; -pub use self::defaultheaders::DefaultHeaders; -pub use self::logger::Logger; -pub use self::normalize::NormalizePath; diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs deleted file mode 100644 index f6b834bfe..000000000 --- a/src/middleware/normalize.rs +++ /dev/null @@ -1,159 +0,0 @@ -//! `Middleware` to normalize request's URI -use std::task::{Context, Poll}; - -use actix_http::http::{PathAndQuery, Uri}; -use actix_service::{Service, Transform}; -use bytes::Bytes; -use futures::future::{ok, Ready}; -use regex::Regex; - -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::Error; - -#[derive(Default, Clone, Copy)] -/// `Middleware` to normalize request's URI in place -/// -/// Performs following: -/// -/// - Merges multiple slashes into one. -/// -/// ```rust -/// use actix_web::{web, http, middleware, App, HttpResponse}; -/// -/// # fn main() { -/// let app = App::new() -/// .wrap(middleware::NormalizePath) -/// .service( -/// web::resource("/test") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// # } -/// ``` - -pub struct NormalizePath; - -impl Transform for NormalizePath -where - S: Service, Error = Error>, - S::Future: 'static, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Transform = NormalizePathNormalization; - type Future = Ready>; - - fn new_transform(&self, service: S) -> Self::Future { - ok(NormalizePathNormalization { - service, - merge_slash: Regex::new("//+").unwrap(), - }) - } -} - -pub struct NormalizePathNormalization { - service: S, - merge_slash: Regex, -} - -impl Service for NormalizePathNormalization -where - S: Service, Error = Error>, - S::Future: 'static, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Future = S::Future; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } - - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - let head = req.head_mut(); - let path = head.uri.path(); - let original_len = path.len(); - let path = self.merge_slash.replace_all(path, "/"); - - if original_len != path.len() { - let mut parts = head.uri.clone().into_parts(); - let pq = parts.path_and_query.as_ref().unwrap(); - - let path = if let Some(q) = pq.query() { - Bytes::from(format!("{}?{}", path, q)) - } else { - Bytes::copy_from_slice(path.as_bytes()) - }; - parts.path_and_query = Some(PathAndQuery::from_maybe_shared(path).unwrap()); - - let uri = Uri::from_parts(parts).unwrap(); - req.match_info_mut().get_mut().update(&uri); - req.head_mut().uri = uri; - } - - self.service.call(req) - } -} - -#[cfg(test)] -mod tests { - use actix_service::IntoService; - - use super::*; - use crate::dev::ServiceRequest; - use crate::test::{call_service, init_service, TestRequest}; - use crate::{web, App, HttpResponse}; - - #[actix_rt::test] - async fn test_wrap() { - let mut app = init_service( - App::new() - .wrap(NormalizePath::default()) - .service(web::resource("/v1/something/").to(|| HttpResponse::Ok())), - ) - .await; - - let req = TestRequest::with_uri("/v1//something////").to_request(); - let res = call_service(&mut app, req).await; - assert!(res.status().is_success()); - } - - #[actix_rt::test] - async fn test_in_place_normalization() { - let srv = |req: ServiceRequest| { - assert_eq!("/v1/something/", req.path()); - ok(req.into_response(HttpResponse::Ok().finish())) - }; - - let mut normalize = NormalizePath - .new_transform(srv.into_service()) - .await - .unwrap(); - - let req = TestRequest::with_uri("/v1//something////").to_srv_request(); - let res = normalize.call(req).await.unwrap(); - assert!(res.status().is_success()); - } - - #[actix_rt::test] - async fn should_normalize_nothing() { - const URI: &str = "/v1/something/"; - - let srv = |req: ServiceRequest| { - assert_eq!(URI, req.path()); - ok(req.into_response(HttpResponse::Ok().finish())) - }; - - let mut normalize = NormalizePath - .new_transform(srv.into_service()) - .await - .unwrap(); - - let req = TestRequest::with_uri(URI).to_srv_request(); - let res = normalize.call(req).await.unwrap(); - assert!(res.status().is_success()); - } -} diff --git a/src/request.rs b/src/request.rs deleted file mode 100644 index cd9c72313..000000000 --- a/src/request.rs +++ /dev/null @@ -1,531 +0,0 @@ -use std::cell::{Ref, RefCell, RefMut}; -use std::rc::Rc; -use std::{fmt, net}; - -use actix_http::http::{HeaderMap, Method, Uri, Version}; -use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead}; -use actix_router::{Path, Url}; -use futures::future::{ok, Ready}; - -use crate::config::AppConfig; -use crate::error::UrlGenerationError; -use crate::extract::FromRequest; -use crate::info::ConnectionInfo; -use crate::rmap::ResourceMap; - -#[derive(Clone)] -/// An HTTP Request -pub struct HttpRequest(pub(crate) Rc); - -pub(crate) struct HttpRequestInner { - pub(crate) head: Message, - pub(crate) path: Path, - pub(crate) payload: Payload, - pub(crate) app_data: Rc, - rmap: Rc, - config: AppConfig, - pool: &'static HttpRequestPool, -} - -impl HttpRequest { - #[inline] - pub(crate) fn new( - path: Path, - head: Message, - payload: Payload, - rmap: Rc, - config: AppConfig, - app_data: Rc, - pool: &'static HttpRequestPool, - ) -> HttpRequest { - HttpRequest(Rc::new(HttpRequestInner { - head, - path, - payload, - rmap, - config, - app_data, - pool, - })) - } -} - -impl HttpRequest { - /// This method returns reference to the request head - #[inline] - pub fn head(&self) -> &RequestHead { - &self.0.head - } - - /// This method returns muttable reference to the request head. - /// panics if multiple references of http request exists. - #[inline] - pub(crate) fn head_mut(&mut self) -> &mut RequestHead { - &mut Rc::get_mut(&mut self.0).unwrap().head - } - - /// 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.0.path - } - - #[inline] - pub(crate) fn match_info_mut(&mut self) -> &mut Path { - &mut Rc::get_mut(&mut self.0).unwrap().path - } - - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref<'_, Extensions> { - self.head().extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.head().extensions_mut() - } - - /// Generate url for named resource - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::{web, App, HttpRequest, HttpResponse}; - /// # - /// fn index(req: HttpRequest) -> HttpResponse { - /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource - /// HttpResponse::Ok().into() - /// } - /// - /// fn main() { - /// let app = App::new() - /// .service(web::resource("/test/{one}/{two}/{three}") - /// .name("foo") // <- set resource name, then it could be used in `url_for` - /// .route(web::get().to(|| HttpResponse::Ok())) - /// ); - /// } - /// ``` - pub fn url_for( - &self, - name: &str, - elements: U, - ) -> Result - where - U: IntoIterator, - I: AsRef, - { - self.0.rmap.url_for(&self, name, elements) - } - - /// Generate url for named resource - /// - /// This method is similar to `HttpRequest::url_for()` but it can be used - /// for urls that do not contain variable parts. - pub fn url_for_static(&self, name: &str) -> Result { - const NO_PARAMS: [&str; 0] = []; - self.url_for(name, &NO_PARAMS) - } - - #[inline] - /// Get a reference to a `ResourceMap` of current application. - pub fn resource_map(&self) -> &ResourceMap { - &self.0.rmap - } - - /// Peer socket address - /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. - /// - /// To get client connection information `.connection_info()` should be used. - #[inline] - pub fn peer_addr(&self) -> Option { - self.head().peer_addr - } - - /// Get *ConnectionInfo* for the current request. - /// - /// This method panics if request's extensions container is already - /// borrowed. - #[inline] - pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { - ConnectionInfo::get(self.head(), &*self.app_config()) - } - - /// App config - #[inline] - pub fn app_config(&self) -> &AppConfig { - &self.0.config - } - - /// Get an application data object stored with `App::data` or `App::app_data` - /// methods during application configuration. - /// - /// If `App::data` was used to store object, use `Data`: - /// - /// ```rust,ignore - /// let opt_t = req.app_data::>(); - /// ``` - pub fn app_data(&self) -> Option<&T> { - if let Some(st) = self.0.app_data.get::() { - Some(&st) - } else { - None - } - } -} - -impl HttpMessage for HttpRequest { - type Stream = (); - - #[inline] - /// Returns Request's headers. - fn headers(&self) -> &HeaderMap { - &self.head().headers - } - - /// Request extensions - #[inline] - fn extensions(&self) -> Ref<'_, Extensions> { - self.0.head.extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.0.head.extensions_mut() - } - - #[inline] - fn take_payload(&mut self) -> Payload { - Payload::None - } -} - -impl Drop for HttpRequest { - fn drop(&mut self) { - if Rc::strong_count(&self.0) == 1 { - let v = &mut self.0.pool.0.borrow_mut(); - if v.len() < 128 { - self.extensions_mut().clear(); - v.push(self.0.clone()); - } - } - } -} - -/// It is possible to get `HttpRequest` as an extractor handler parameter -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App, HttpRequest}; -/// use serde_derive::Deserialize; -/// -/// /// extract `Thing` from request -/// async fn index(req: HttpRequest) -> String { -/// format!("Got thing: {:?}", req) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/users/{first}").route( -/// web::get().to(index)) -/// ); -/// } -/// ``` -impl FromRequest for HttpRequest { - type Config = (); - type Error = Error; - type Future = Ready>; - - #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - ok(req.clone()) - } -} - -impl fmt::Debug for HttpRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!( - f, - "\nHttpRequest {:?} {}:{}", - self.0.head.version, - self.0.head.method, - self.path() - )?; - if !self.query_string().is_empty() { - writeln!(f, " query: ?{:?}", self.query_string())?; - } - if !self.match_info().is_empty() { - writeln!(f, " params: {:?}", self.match_info())?; - } - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -/// Request's objects pool -pub(crate) struct HttpRequestPool(RefCell>>); - -impl HttpRequestPool { - pub(crate) fn create() -> &'static HttpRequestPool { - let pool = HttpRequestPool(RefCell::new(Vec::with_capacity(128))); - Box::leak(Box::new(pool)) - } - - /// Get message from the pool - #[inline] - pub(crate) fn get_request(&self) -> Option { - if let Some(inner) = self.0.borrow_mut().pop() { - Some(HttpRequest(inner)) - } else { - None - } - } - - pub(crate) fn clear(&self) { - self.0.borrow_mut().clear() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::dev::{ResourceDef, ResourceMap}; - use crate::http::{header, StatusCode}; - use crate::test::{call_service, init_service, TestRequest}; - use crate::{web, App, HttpResponse}; - - #[test] - fn test_debug() { - let req = - TestRequest::with_header("content-type", "text/plain").to_http_request(); - let dbg = format!("{:?}", req); - assert!(dbg.contains("HttpRequest")); - } - - #[test] - fn test_no_request_cookies() { - let req = TestRequest::default().to_http_request(); - assert!(req.cookies().unwrap().is_empty()); - } - - #[test] - fn test_request_cookies() { - let req = TestRequest::default() - .header(header::COOKIE, "cookie1=value1") - .header(header::COOKIE, "cookie2=value2") - .to_http_request(); - { - let cookies = req.cookies().unwrap(); - assert_eq!(cookies.len(), 2); - assert_eq!(cookies[0].name(), "cookie2"); - assert_eq!(cookies[0].value(), "value2"); - assert_eq!(cookies[1].name(), "cookie1"); - assert_eq!(cookies[1].value(), "value1"); - } - - let cookie = req.cookie("cookie1"); - assert!(cookie.is_some()); - let cookie = cookie.unwrap(); - assert_eq!(cookie.name(), "cookie1"); - assert_eq!(cookie.value(), "value1"); - - let cookie = req.cookie("cookie-unknown"); - assert!(cookie.is_none()); - } - - #[test] - fn test_request_query() { - let req = TestRequest::with_uri("/?id=test").to_http_request(); - assert_eq!(req.query_string(), "id=test"); - } - - #[test] - fn test_url_for() { - let mut res = ResourceDef::new("/user/{name}.{ext}"); - *res.name_mut() = "index".to_string(); - - let mut rmap = ResourceMap::new(ResourceDef::new("")); - rmap.add(&mut res, None); - assert!(rmap.has_resource("/user/test.html")); - assert!(!rmap.has_resource("/test/unknown")); - - let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") - .rmap(rmap) - .to_http_request(); - - assert_eq!( - req.url_for("unknown", &["test"]), - Err(UrlGenerationError::ResourceNotFound) - ); - assert_eq!( - req.url_for("index", &["test"]), - Err(UrlGenerationError::NotEnoughElements) - ); - let url = req.url_for("index", &["test", "html"]); - assert_eq!( - url.ok().unwrap().as_str(), - "http://www.rust-lang.org/user/test.html" - ); - } - - #[test] - fn test_url_for_static() { - let mut rdef = ResourceDef::new("/index.html"); - *rdef.name_mut() = "index".to_string(); - - let mut rmap = ResourceMap::new(ResourceDef::new("")); - rmap.add(&mut rdef, None); - - assert!(rmap.has_resource("/index.html")); - - let req = TestRequest::with_uri("/test") - .header(header::HOST, "www.rust-lang.org") - .rmap(rmap) - .to_http_request(); - let url = req.url_for_static("index"); - assert_eq!( - url.ok().unwrap().as_str(), - "http://www.rust-lang.org/index.html" - ); - } - - #[test] - fn test_url_for_external() { - let mut rdef = ResourceDef::new("https://youtube.com/watch/{video_id}"); - - *rdef.name_mut() = "youtube".to_string(); - - let mut rmap = ResourceMap::new(ResourceDef::new("")); - rmap.add(&mut rdef, None); - assert!(rmap.has_resource("https://youtube.com/watch/unknown")); - - let req = TestRequest::default().rmap(rmap).to_http_request(); - let url = req.url_for("youtube", &["oHg5SJYRHA0"]); - assert_eq!( - url.ok().unwrap().as_str(), - "https://youtube.com/watch/oHg5SJYRHA0" - ); - } - - #[actix_rt::test] - async fn test_data() { - let mut srv = init_service(App::new().app_data(10usize).service( - web::resource("/").to(|req: HttpRequest| { - if req.app_data::().is_some() { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - )) - .await; - - let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - - let mut srv = init_service(App::new().app_data(10u32).service( - web::resource("/").to(|req: HttpRequest| { - if req.app_data::().is_some() { - HttpResponse::Ok() - } else { - HttpResponse::BadRequest() - } - }), - )) - .await; - - let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } - - #[actix_rt::test] - async fn test_extensions_dropped() { - struct Tracker { - pub dropped: bool, - } - struct Foo { - tracker: Rc>, - } - impl Drop for Foo { - fn drop(&mut self) { - self.tracker.borrow_mut().dropped = true; - } - } - - let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); - { - let tracker2 = Rc::clone(&tracker); - let mut srv = init_service(App::new().data(10u32).service( - web::resource("/").to(move |req: HttpRequest| { - req.extensions_mut().insert(Foo { - tracker: Rc::clone(&tracker2), - }); - HttpResponse::Ok() - }), - )) - .await; - - let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - } - - assert!(tracker.borrow().dropped); - } -} diff --git a/src/resource.rs b/src/resource.rs deleted file mode 100644 index d03024a07..000000000 --- a/src/resource.rs +++ /dev/null @@ -1,796 +0,0 @@ -use std::cell::RefCell; -use std::fmt; -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; - -use actix_http::{Error, Extensions, Response}; -use actix_router::IntoPattern; -use actix_service::boxed::{self, BoxService, BoxServiceFactory}; -use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, -}; -use futures::future::{ok, Either, LocalBoxFuture, Ready}; - -use crate::data::Data; -use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; -use crate::extract::FromRequest; -use crate::guard::Guard; -use crate::handler::Factory; -use crate::responder::Responder; -use crate::route::{CreateRouteService, Route, RouteService}; -use crate::service::{ServiceRequest, ServiceResponse}; - -type HttpService = BoxService; -type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; - -/// *Resource* is an entry in resources table which corresponds to requested URL. -/// -/// Resource in turn has at least one route. -/// Route consists of an handlers objects and list of guards -/// (objects that implement `Guard` trait). -/// Resources and routes uses builder-like pattern for configuration. -/// During request handling, resource object iterate through all routes -/// and check guards for specific route, if request matches all -/// guards, route considered matched and route handler get called. -/// -/// ```rust -/// use actix_web::{web, App, HttpResponse}; -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/") -/// .route(web::get().to(|| HttpResponse::Ok()))); -/// } -/// ``` -/// -/// If no matching route could be found, *405* response code get returned. -/// Default behavior could be overriden with `default_resource()` method. -pub struct Resource { - endpoint: T, - rdef: Vec, - name: Option, - routes: Vec, - data: Option, - guards: Vec>, - default: Rc>>>, - factory_ref: Rc>>, -} - -impl Resource { - pub fn new(path: T) -> Resource { - let fref = Rc::new(RefCell::new(None)); - - Resource { - routes: Vec::new(), - rdef: path.patterns(), - name: None, - endpoint: ResourceEndpoint::new(fref.clone()), - factory_ref: fref, - guards: Vec::new(), - data: None, - default: Rc::new(RefCell::new(None)), - } - } -} - -impl Resource -where - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ - /// Set resource name. - /// - /// Name is used for url generation. - pub fn name(mut self, name: &str) -> Self { - self.name = Some(name.to_string()); - self - } - - /// Add match guard to a resource. - /// - /// ```rust - /// use actix_web::{web, guard, App, HttpResponse}; - /// - /// async fn index(data: web::Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new() - /// .service( - /// web::resource("/app") - /// .guard(guard::Header("content-type", "text/plain")) - /// .route(web::get().to(index)) - /// ) - /// .service( - /// web::resource("/app") - /// .guard(guard::Header("content-type", "text/json")) - /// .route(web::get().to(|| HttpResponse::MethodNotAllowed())) - /// ); - /// } - /// ``` - pub fn guard(mut self, guard: G) -> Self { - self.guards.push(Box::new(guard)); - self - } - - pub(crate) fn add_guards(mut self, guards: Vec>) -> Self { - self.guards.extend(guards); - self - } - - /// Register a new route. - /// - /// ```rust - /// use actix_web::{web, guard, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/").route( - /// web::route() - /// .guard(guard::Any(guard::Get()).or(guard::Put())) - /// .guard(guard::Header("Content-Type", "text/plain")) - /// .to(|| HttpResponse::Ok())) - /// ); - /// } - /// ``` - /// - /// Multiple routes could be added to a resource. Resource object uses - /// match guards for route selection. - /// - /// ```rust - /// use actix_web::{web, guard, App}; - /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/container/") - /// .route(web::get().to(get_handler)) - /// .route(web::post().to(post_handler)) - /// .route(web::delete().to(delete_handler)) - /// ); - /// } - /// # async fn get_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() } - /// # async fn post_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() } - /// # async fn delete_handler() -> impl actix_web::Responder { actix_web::HttpResponse::Ok() } - /// ``` - pub fn route(mut self, route: Route) -> Self { - self.routes.push(route); - self - } - - /// Provide resource specific data. This method allows to add extractor - /// configuration or specific state available via `Data` extractor. - /// Provided data is available for all routes registered for the current resource. - /// Resource data overrides data registered by `App::data()` method. - /// - /// ```rust - /// use actix_web::{web, App, FromRequest}; - /// - /// /// extract text data from request - /// async fn index(body: String) -> String { - /// format!("Body {}!", body) - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/index.html") - /// // limit size of the payload - /// .data(String::configure(|cfg| { - /// cfg.limit(4096) - /// })) - /// .route( - /// web::get() - /// // register handler - /// .to(index) - /// )); - /// } - /// ``` - pub fn data(self, data: U) -> Self { - self.app_data(Data::new(data)) - } - - /// Set or override application data. - /// - /// This method overrides data stored with [`App::app_data()`](#method.app_data) - pub fn app_data(mut self, data: U) -> Self { - if self.data.is_none() { - self.data = Some(Extensions::new()); - } - self.data.as_mut().unwrap().insert(data); - self - } - - /// Register a new route and add handler. This route matches all requests. - /// - /// ```rust - /// use actix_web::*; - /// - /// fn index(req: HttpRequest) -> HttpResponse { - /// unimplemented!() - /// } - /// - /// App::new().service(web::resource("/").to(index)); - /// ``` - /// - /// This is shortcut for: - /// - /// ```rust - /// # extern crate actix_web; - /// # use actix_web::*; - /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().service(web::resource("/").route(web::route().to(index))); - /// ``` - pub fn to(mut self, handler: F) -> Self - where - F: Factory, - I: FromRequest + 'static, - R: Future + 'static, - U: Responder + 'static, - { - self.routes.push(Route::new().to(handler)); - self - } - - /// Register a resource middleware. - /// - /// This is similar to `App's` middlewares, but middleware get invoked on resource level. - /// Resource level middlewares are not allowed to change response - /// type (i.e modify response's body). - /// - /// **Note**: middlewares get called in opposite order of middlewares registration. - pub fn wrap( - self, - mw: M, - ) -> Resource< - impl ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - > - where - M: Transform< - T::Service, - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - { - Resource { - endpoint: apply(mw, self.endpoint), - rdef: self.rdef, - name: self.name, - guards: self.guards, - routes: self.routes, - default: self.default, - data: self.data, - factory_ref: self.factory_ref, - } - } - - /// Register a resource middleware function. - /// - /// This function accepts instance of `ServiceRequest` type and - /// mutable reference to the next middleware in chain. - /// - /// This is similar to `App's` middlewares, but middleware get invoked on resource level. - /// Resource level middlewares are not allowed to change response - /// type (i.e modify response's body). - /// - /// ```rust - /// use actix_service::Service; - /// use actix_web::{web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; - /// - /// async fn index() -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/index.html") - /// .wrap_fn(|req, srv| { - /// let fut = srv.call(req); - /// async { - /// let mut res = fut.await?; - /// res.headers_mut().insert( - /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), - /// ); - /// Ok(res) - /// } - /// }) - /// .route(web::get().to(index))); - /// } - /// ``` - pub fn wrap_fn( - self, - mw: F, - ) -> Resource< - impl ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - > - where - F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, - R: Future>, - { - Resource { - endpoint: apply_fn_factory(self.endpoint, mw), - rdef: self.rdef, - name: self.name, - guards: self.guards, - routes: self.routes, - default: self.default, - data: self.data, - factory_ref: self.factory_ref, - } - } - - /// Default service to be used if no matching route could be found. - /// By default *405* response get returned. Resource does not use - /// default handler from `App` or `Scope`. - pub fn default_service(mut self, f: F) -> Self - where - F: IntoServiceFactory, - U: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - > + 'static, - U::InitError: fmt::Debug, - { - // create and configure default resource - self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory( - f.into_factory().map_init_err(|e| { - log::error!("Can not construct default service: {:?}", e) - }), - ))))); - - self - } -} - -impl HttpServiceFactory for Resource -where - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - > + 'static, -{ - fn register(mut self, config: &mut AppService) { - let guards = if self.guards.is_empty() { - None - } else { - Some(std::mem::replace(&mut self.guards, Vec::new())) - }; - let mut rdef = if config.is_root() || !self.rdef.is_empty() { - ResourceDef::new(insert_slash(self.rdef.clone())) - } else { - ResourceDef::new(self.rdef.clone()) - }; - if let Some(ref name) = self.name { - *rdef.name_mut() = name.clone(); - } - // custom app data storage - if let Some(ref mut ext) = self.data { - config.set_service_data(ext); - } - config.register_service(rdef, guards, self, None) - } -} - -impl IntoServiceFactory for Resource -where - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ - fn into_factory(self) -> T { - *self.factory_ref.borrow_mut() = Some(ResourceFactory { - routes: self.routes, - data: self.data.map(Rc::new), - default: self.default, - }); - - self.endpoint - } -} - -pub struct ResourceFactory { - routes: Vec, - data: Option>, - default: Rc>>>, -} - -impl ServiceFactory for ResourceFactory { - type Config = (); - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Service = ResourceService; - type Future = CreateResourceService; - - fn new_service(&self, _: ()) -> Self::Future { - let default_fut = if let Some(ref default) = *self.default.borrow() { - Some(default.new_service(())) - } else { - None - }; - - CreateResourceService { - fut: self - .routes - .iter() - .map(|route| CreateRouteServiceItem::Future(route.new_service(()))) - .collect(), - data: self.data.clone(), - default: None, - default_fut, - } - } -} - -enum CreateRouteServiceItem { - Future(CreateRouteService), - Service(RouteService), -} - -pub struct CreateResourceService { - fut: Vec, - data: Option>, - default: Option, - default_fut: Option>>, -} - -impl Future for CreateResourceService { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut done = true; - - if let Some(ref mut fut) = self.default_fut { - match Pin::new(fut).poll(cx)? { - Poll::Ready(default) => self.default = Some(default), - Poll::Pending => done = false, - } - } - - // poll http services - for item in &mut self.fut { - match item { - CreateRouteServiceItem::Future(ref mut fut) => match Pin::new(fut) - .poll(cx)? - { - Poll::Ready(route) => *item = CreateRouteServiceItem::Service(route), - Poll::Pending => { - done = false; - } - }, - CreateRouteServiceItem::Service(_) => continue, - }; - } - - if done { - let routes = self - .fut - .drain(..) - .map(|item| match item { - CreateRouteServiceItem::Service(service) => service, - CreateRouteServiceItem::Future(_) => unreachable!(), - }) - .collect(); - Poll::Ready(Ok(ResourceService { - routes, - data: self.data.clone(), - default: self.default.take(), - })) - } else { - Poll::Pending - } - } -} - -pub struct ResourceService { - routes: Vec, - data: Option>, - default: Option, -} - -impl Service for ResourceService { - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Future = Either< - Ready>, - LocalBoxFuture<'static, Result>, - >; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - for route in self.routes.iter_mut() { - if route.check(&mut req) { - if let Some(ref data) = self.data { - req.set_data_container(data.clone()); - } - return Either::Right(route.call(req)); - } - } - if let Some(ref mut default) = self.default { - Either::Right(default.call(req)) - } else { - let req = req.into_parts().0; - Either::Left(ok(ServiceResponse::new( - req, - Response::MethodNotAllowed().finish(), - ))) - } - } -} - -#[doc(hidden)] -pub struct ResourceEndpoint { - factory: Rc>>, -} - -impl ResourceEndpoint { - fn new(factory: Rc>>) -> Self { - ResourceEndpoint { factory } - } -} - -impl ServiceFactory for ResourceEndpoint { - type Config = (); - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Service = ResourceService; - type Future = CreateResourceService; - - fn new_service(&self, _: ()) -> Self::Future { - self.factory.borrow_mut().as_mut().unwrap().new_service(()) - } -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use actix_rt::time::delay_for; - use actix_service::Service; - use futures::future::ok; - - use crate::http::{header, HeaderValue, Method, StatusCode}; - use crate::middleware::DefaultHeaders; - use crate::service::ServiceRequest; - use crate::test::{call_service, init_service, TestRequest}; - use crate::{guard, web, App, Error, HttpResponse}; - - #[actix_rt::test] - async fn test_middleware() { - let mut srv = - init_service( - App::new().service( - web::resource("/test") - .name("test") - .wrap(DefaultHeaders::new().header( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - )) - .route(web::get().to(|| HttpResponse::Ok())), - ), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - } - - #[actix_rt::test] - async fn test_middleware_fn() { - let mut srv = init_service( - App::new().service( - web::resource("/test") - .wrap_fn(|req, srv| { - let fut = srv.call(req); - async { - fut.await.map(|mut res| { - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); - res - }) - } - }) - .route(web::get().to(|| HttpResponse::Ok())), - ), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - } - - #[actix_rt::test] - async fn test_to() { - let mut srv = - init_service(App::new().service(web::resource("/test").to(|| { - async { - delay_for(Duration::from_millis(100)).await; - Ok::<_, Error>(HttpResponse::Ok()) - } - }))) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_pattern() { - let mut srv = init_service( - App::new().service( - web::resource(["/test", "/test2"]) - .to(|| async { Ok::<_, Error>(HttpResponse::Ok()) }), - ), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - let req = TestRequest::with_uri("/test2").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_default_resource() { - let mut srv = init_service( - App::new() - .service( - web::resource("/test").route(web::get().to(|| HttpResponse::Ok())), - ) - .default_service(|r: ServiceRequest| { - ok(r.into_response(HttpResponse::BadRequest())) - }), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let mut srv = init_service( - App::new().service( - web::resource("/test") - .route(web::get().to(|| HttpResponse::Ok())) - .default_service(|r: ServiceRequest| { - ok(r.into_response(HttpResponse::BadRequest())) - }), - ), - ) - .await; - - let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } - - #[actix_rt::test] - async fn test_resource_guards() { - let mut srv = init_service( - App::new() - .service( - web::resource("/test/{p}") - .guard(guard::Get()) - .to(|| HttpResponse::Ok()), - ) - .service( - web::resource("/test/{p}") - .guard(guard::Put()) - .to(|| HttpResponse::Created()), - ) - .service( - web::resource("/test/{p}") - .guard(guard::Delete()) - .to(|| HttpResponse::NoContent()), - ), - ) - .await; - - let req = TestRequest::with_uri("/test/it") - .method(Method::GET) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test/it") - .method(Method::PUT) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::CREATED); - - let req = TestRequest::with_uri("/test/it") - .method(Method::DELETE) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::NO_CONTENT); - } - - #[actix_rt::test] - async fn test_data() { - let mut srv = init_service( - App::new() - .data(1.0f64) - .data(1usize) - .app_data(web::Data::new('-')) - .service( - web::resource("/test") - .data(10usize) - .app_data(web::Data::new('*')) - .guard(guard::Get()) - .to( - |data1: web::Data, - data2: web::Data, - data3: web::Data| { - assert_eq!(**data1, 10); - assert_eq!(**data2, '*'); - assert_eq!(**data3, 1.0); - HttpResponse::Ok() - }, - ), - ), - ) - .await; - - let req = TestRequest::get().uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - } -} diff --git a/src/responder.rs b/src/responder.rs deleted file mode 100644 index 7189eecf1..000000000 --- a/src/responder.rs +++ /dev/null @@ -1,656 +0,0 @@ -use std::convert::TryFrom; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix_http::error::InternalError; -use actix_http::http::{ - header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, StatusCode, -}; -use actix_http::{Error, Response, ResponseBuilder}; -use bytes::{Bytes, BytesMut}; -use futures::future::{err, ok, Either as EitherFuture, Ready}; -use futures::ready; -use pin_project::{pin_project, project}; - -use crate::request::HttpRequest; - -/// Trait implemented by types that can be converted to a http response. -/// -/// Types that implement this trait can be used as the return type of a handler. -pub trait Responder { - /// The associated error which can be returned. - type Error: Into; - - /// The future response value. - type Future: Future>; - - /// Convert itself to `AsyncResult` or `Error`. - fn respond_to(self, req: &HttpRequest) -> Self::Future; - - /// Override a status code for a Responder. - /// - /// ```rust - /// use actix_web::{HttpRequest, Responder, http::StatusCode}; - /// - /// fn index(req: HttpRequest) -> impl Responder { - /// "Welcome!".with_status(StatusCode::OK) - /// } - /// # fn main() {} - /// ``` - fn with_status(self, status: StatusCode) -> CustomResponder - where - Self: Sized, - { - CustomResponder::new(self).with_status(status) - } - - /// Add header to the Responder's response. - /// - /// ```rust - /// use actix_web::{web, HttpRequest, Responder}; - /// use serde::Serialize; - /// - /// #[derive(Serialize)] - /// struct MyObj { - /// name: String, - /// } - /// - /// fn index(req: HttpRequest) -> impl Responder { - /// web::Json( - /// MyObj{name: "Name".to_string()} - /// ) - /// .with_header("x-version", "1.2.3") - /// } - /// # fn main() {} - /// ``` - fn with_header(self, key: K, value: V) -> CustomResponder - where - Self: Sized, - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - CustomResponder::new(self).with_header(key, value) - } -} - -impl Responder for Response { - type Error = Error; - type Future = Ready>; - - #[inline] - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(self) - } -} - -impl Responder for Option -where - T: Responder, -{ - type Error = T::Error; - type Future = EitherFuture>>; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - match self { - Some(t) => EitherFuture::Left(t.respond_to(req)), - None => { - EitherFuture::Right(ok(Response::build(StatusCode::NOT_FOUND).finish())) - } - } - } -} - -impl Responder for Result -where - T: Responder, - E: Into, -{ - type Error = Error; - type Future = EitherFuture< - ResponseFuture, - Ready>, - >; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - match self { - Ok(val) => EitherFuture::Left(ResponseFuture::new(val.respond_to(req))), - Err(e) => EitherFuture::Right(err(e.into())), - } - } -} - -impl Responder for ResponseBuilder { - type Error = Error; - type Future = Ready>; - - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> Self::Future { - ok(self.finish()) - } -} - -impl Responder for (T, StatusCode) -where - T: Responder, -{ - type Error = T::Error; - type Future = CustomResponderFut; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - CustomResponderFut { - fut: self.0.respond_to(req), - status: Some(self.1), - headers: None, - } - } -} - -impl Responder for &'static str { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - -impl Responder for &'static [u8] { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - -impl Responder for String { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - -impl<'a> Responder for &'a String { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) - .content_type("text/plain; charset=utf-8") - .body(self)) - } -} - -impl Responder for Bytes { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - -impl Responder for BytesMut { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) - .content_type("application/octet-stream") - .body(self)) - } -} - -/// Allows to override status code and headers for a responder. -pub struct CustomResponder { - responder: T, - status: Option, - headers: Option, - error: Option, -} - -impl CustomResponder { - fn new(responder: T) -> Self { - CustomResponder { - responder, - status: None, - headers: None, - error: None, - } - } - - /// Override a status code for the Responder's response. - /// - /// ```rust - /// use actix_web::{HttpRequest, Responder, http::StatusCode}; - /// - /// fn index(req: HttpRequest) -> impl Responder { - /// "Welcome!".with_status(StatusCode::OK) - /// } - /// # fn main() {} - /// ``` - pub fn with_status(mut self, status: StatusCode) -> Self { - self.status = Some(status); - self - } - - /// Add header to the Responder's response. - /// - /// ```rust - /// use actix_web::{web, HttpRequest, Responder}; - /// use serde::Serialize; - /// - /// #[derive(Serialize)] - /// struct MyObj { - /// name: String, - /// } - /// - /// fn index(req: HttpRequest) -> impl Responder { - /// web::Json( - /// MyObj{name: "Name".to_string()} - /// ) - /// .with_header("x-version", "1.2.3") - /// } - /// # fn main() {} - /// ``` - pub fn with_header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - if self.headers.is_none() { - self.headers = Some(HeaderMap::new()); - } - - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - self.headers.as_mut().unwrap().append(key, value); - } - Err(e) => self.error = Some(e.into()), - }, - Err(e) => self.error = Some(e.into()), - }; - self - } -} - -impl Responder for CustomResponder { - type Error = T::Error; - type Future = CustomResponderFut; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - CustomResponderFut { - fut: self.responder.respond_to(req), - status: self.status, - headers: self.headers, - } - } -} - -#[pin_project] -pub struct CustomResponderFut { - #[pin] - fut: T::Future, - status: Option, - headers: Option, -} - -impl Future for CustomResponderFut { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - let mut res = match ready!(this.fut.poll(cx)) { - Ok(res) => res, - Err(e) => return Poll::Ready(Err(e)), - }; - if let Some(status) = this.status.take() { - *res.status_mut() = status; - } - if let Some(ref headers) = this.headers { - for (k, v) in headers { - res.headers_mut().insert(k.clone(), v.clone()); - } - } - Poll::Ready(Ok(res)) - } -} - -/// Combines two different responder types into a single type -/// -/// ```rust -/// use actix_web::{Either, Error, HttpResponse}; -/// -/// type RegisterResult = Either>; -/// -/// fn index() -> RegisterResult { -/// if is_a_variant() { -/// // <- choose left variant -/// Either::A(HttpResponse::BadRequest().body("Bad data")) -/// } else { -/// Either::B( -/// // <- Right variant -/// Ok(HttpResponse::Ok() -/// .content_type("text/html") -/// .body("Hello!")) -/// ) -/// } -/// } -/// # fn is_a_variant() -> bool { true } -/// # fn main() {} -/// ``` -#[derive(Debug, PartialEq)] -pub enum Either { - /// First branch of the type - A(A), - /// Second branch of the type - B(B), -} - -impl Responder for Either -where - A: Responder, - B: Responder, -{ - type Error = Error; - type Future = EitherResponder; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - match self { - Either::A(a) => EitherResponder::A(a.respond_to(req)), - Either::B(b) => EitherResponder::B(b.respond_to(req)), - } - } -} - -#[pin_project] -pub enum EitherResponder -where - A: Responder, - B: Responder, -{ - A(#[pin] A::Future), - B(#[pin] B::Future), -} - -impl Future for EitherResponder -where - A: Responder, - B: Responder, -{ - type Output = Result; - - #[project] - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - #[project] - match self.project() { - EitherResponder::A(fut) => { - Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into())) - } - EitherResponder::B(fut) => { - Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into()))) - } - } - } -} - -impl Responder for InternalError -where - T: std::fmt::Debug + std::fmt::Display + 'static, -{ - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - let err: Error = self.into(); - ok(err.into()) - } -} - -#[pin_project] -pub struct ResponseFuture { - #[pin] - fut: T, - _t: PhantomData, -} - -impl ResponseFuture { - pub fn new(fut: T) -> Self { - ResponseFuture { - fut, - _t: PhantomData, - } - } -} - -impl Future for ResponseFuture -where - T: Future>, - E: Into, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Poll::Ready(ready!(self.project().fut.poll(cx)).map_err(|e| e.into())) - } -} - -#[cfg(test)] -pub(crate) mod tests { - use actix_service::Service; - use bytes::{Bytes, BytesMut}; - - use super::*; - use crate::dev::{Body, ResponseBody}; - use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; - use crate::test::{init_service, TestRequest}; - use crate::{error, web, App, HttpResponse}; - - #[actix_rt::test] - async fn test_option_responder() { - let mut srv = init_service( - App::new() - .service( - web::resource("/none").to(|| async { Option::<&'static str>::None }), - ) - .service(web::resource("/some").to(|| async { Some("some") })), - ) - .await; - - let req = TestRequest::with_uri("/none").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/some").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"some")); - } - _ => panic!(), - } - } - - pub(crate) trait BodyTest { - fn bin_ref(&self) -> &[u8]; - fn body(&self) -> &Body; - } - - impl BodyTest for ResponseBody { - fn bin_ref(&self) -> &[u8] { - match self { - ResponseBody::Body(ref b) => match b { - Body::Bytes(ref bin) => &bin, - _ => panic!(), - }, - ResponseBody::Other(ref b) => match b { - Body::Bytes(ref bin) => &bin, - _ => panic!(), - }, - } - } - fn body(&self) -> &Body { - match self { - ResponseBody::Body(ref b) => b, - ResponseBody::Other(ref b) => b, - } - } - } - - #[actix_rt::test] - async fn test_responder() { - let req = TestRequest::default().to_http_request(); - - let resp: HttpResponse = "test".respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - - let resp: HttpResponse = b"test".respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - - let resp: HttpResponse = "test".to_string().respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - - let resp: HttpResponse = (&"test".to_string()).respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - - let resp: HttpResponse = - Bytes::from_static(b"test").respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - - let resp: HttpResponse = BytesMut::from(b"test".as_ref()) - .respond_to(&req) - .await - .unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/octet-stream") - ); - - // InternalError - let resp: HttpResponse = - error::InternalError::new("err", StatusCode::BAD_REQUEST) - .respond_to(&req) - .await - .unwrap(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - } - - #[actix_rt::test] - async fn test_result_responder() { - let req = TestRequest::default().to_http_request(); - - // Result - let resp: HttpResponse = Ok::<_, Error>("test".to_string()) - .respond_to(&req) - .await - .unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!(resp.body().bin_ref(), b"test"); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("text/plain; charset=utf-8") - ); - - let res = - Err::(error::InternalError::new("err", StatusCode::BAD_REQUEST)) - .respond_to(&req) - .await; - assert!(res.is_err()); - } - - #[actix_rt::test] - async fn test_custom_responder() { - let req = TestRequest::default().to_http_request(); - let res = "test" - .to_string() - .with_status(StatusCode::BAD_REQUEST) - .respond_to(&req) - .await - .unwrap(); - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - assert_eq!(res.body().bin_ref(), b"test"); - - let res = "test" - .to_string() - .with_header("content-type", "json") - .respond_to(&req) - .await - .unwrap(); - - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.body().bin_ref(), b"test"); - assert_eq!( - res.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("json") - ); - } - - #[actix_rt::test] - async fn test_tuple_responder_with_status_code() { - let req = TestRequest::default().to_http_request(); - let res = ("test".to_string(), StatusCode::BAD_REQUEST) - .respond_to(&req) - .await - .unwrap(); - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - assert_eq!(res.body().bin_ref(), b"test"); - - let req = TestRequest::default().to_http_request(); - let res = ("test".to_string(), StatusCode::OK) - .with_header("content-type", "json") - .respond_to(&req) - .await - .unwrap(); - assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.body().bin_ref(), b"test"); - assert_eq!( - res.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("json") - ); - } -} diff --git a/src/rmap.rs b/src/rmap.rs deleted file mode 100644 index 47092608c..000000000 --- a/src/rmap.rs +++ /dev/null @@ -1,190 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; - -use actix_router::ResourceDef; -use fxhash::FxHashMap; -use url::Url; - -use crate::error::UrlGenerationError; -use crate::request::HttpRequest; - -#[derive(Clone, Debug)] -pub struct ResourceMap { - root: ResourceDef, - parent: RefCell>>, - named: FxHashMap, - patterns: Vec<(ResourceDef, Option>)>, -} - -impl ResourceMap { - pub fn new(root: ResourceDef) -> Self { - ResourceMap { - root, - parent: RefCell::new(None), - named: FxHashMap::default(), - patterns: Vec::new(), - } - } - - pub fn add(&mut self, pattern: &mut ResourceDef, nested: Option>) { - pattern.set_id(self.patterns.len() as u16); - self.patterns.push((pattern.clone(), nested)); - if !pattern.name().is_empty() { - self.named - .insert(pattern.name().to_string(), pattern.clone()); - } - } - - pub(crate) fn finish(&self, current: Rc) { - for (_, nested) in &self.patterns { - if let Some(ref nested) = nested { - *nested.parent.borrow_mut() = Some(current.clone()); - nested.finish(nested.clone()); - } - } - } -} - -impl ResourceMap { - /// Generate url for named resource - /// - /// Check [`HttpRequest::url_for()`](../struct.HttpRequest.html#method. - /// url_for) for detailed information. - pub fn url_for( - &self, - req: &HttpRequest, - name: &str, - elements: U, - ) -> Result - where - U: IntoIterator, - I: AsRef, - { - let mut path = String::new(); - let mut elements = elements.into_iter(); - - if self.patterns_for(name, &mut path, &mut elements)?.is_some() { - if path.starts_with('/') { - let conn = req.connection_info(); - Ok(Url::parse(&format!( - "{}://{}{}", - conn.scheme(), - conn.host(), - path - ))?) - } else { - Ok(Url::parse(&path)?) - } - } else { - Err(UrlGenerationError::ResourceNotFound) - } - } - - pub fn has_resource(&self, path: &str) -> bool { - let path = if path.is_empty() { "/" } else { path }; - - for (pattern, rmap) in &self.patterns { - if let Some(ref rmap) = rmap { - if let Some(plen) = pattern.is_prefix_match(path) { - return rmap.has_resource(&path[plen..]); - } - } else if pattern.is_match(path) { - return true; - } - } - false - } - - fn patterns_for( - &self, - name: &str, - path: &mut String, - elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if self.pattern_for(name, path, elements)?.is_some() { - Ok(Some(())) - } else { - self.parent_pattern_for(name, path, elements) - } - } - - fn pattern_for( - &self, - name: &str, - path: &mut String, - elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(pattern) = self.named.get(name) { - if pattern.pattern().starts_with('/') { - self.fill_root(path, elements)?; - } - if pattern.resource_path(path, elements) { - Ok(Some(())) - } else { - Err(UrlGenerationError::NotEnoughElements) - } - } else { - for (_, rmap) in &self.patterns { - if let Some(ref rmap) = rmap { - if rmap.pattern_for(name, path, elements)?.is_some() { - return Ok(Some(())); - } - } - } - Ok(None) - } - } - - fn fill_root( - &self, - path: &mut String, - elements: &mut U, - ) -> Result<(), UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(ref parent) = *self.parent.borrow() { - parent.fill_root(path, elements)?; - } - if self.root.resource_path(path, elements) { - Ok(()) - } else { - Err(UrlGenerationError::NotEnoughElements) - } - } - - fn parent_pattern_for( - &self, - name: &str, - path: &mut String, - elements: &mut U, - ) -> Result, UrlGenerationError> - where - U: Iterator, - I: AsRef, - { - if let Some(ref parent) = *self.parent.borrow() { - if let Some(pattern) = parent.named.get(name) { - self.fill_root(path, elements)?; - if pattern.resource_path(path, elements) { - Ok(Some(())) - } else { - Err(UrlGenerationError::NotEnoughElements) - } - } else { - parent.parent_pattern_for(name, path, elements) - } - } else { - Ok(None) - } - } -} diff --git a/src/route.rs b/src/route.rs deleted file mode 100644 index f7e391746..000000000 --- a/src/route.rs +++ /dev/null @@ -1,431 +0,0 @@ -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; - -use actix_http::{http::Method, Error}; -use actix_service::{Service, ServiceFactory}; -use futures::future::{ready, FutureExt, LocalBoxFuture}; - -use crate::extract::FromRequest; -use crate::guard::{self, Guard}; -use crate::handler::{Extract, Factory, Handler}; -use crate::responder::Responder; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::HttpResponse; - -type BoxedRouteService = Box< - dyn Service< - Request = Req, - Response = Res, - Error = Error, - Future = LocalBoxFuture<'static, Result>, - >, ->; - -type BoxedRouteNewService = Box< - dyn ServiceFactory< - Config = (), - Request = Req, - Response = Res, - Error = Error, - InitError = (), - Service = BoxedRouteService, - Future = LocalBoxFuture<'static, Result, ()>>, - >, ->; - -/// 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 Route { - service: BoxedRouteNewService, - guards: Rc>>, -} - -impl Route { - /// Create new route which matches any request. - pub fn new() -> Route { - Route { - service: Box::new(RouteNewService::new(Extract::new(Handler::new(|| { - ready(HttpResponse::NotFound()) - })))), - guards: Rc::new(Vec::new()), - } - } - - pub(crate) fn take_guards(&mut self) -> Vec> { - std::mem::replace(Rc::get_mut(&mut self.guards).unwrap(), Vec::new()) - } -} - -impl ServiceFactory for Route { - type Config = (); - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Service = RouteService; - type Future = CreateRouteService; - - fn new_service(&self, _: ()) -> Self::Future { - CreateRouteService { - fut: self.service.new_service(()), - guards: self.guards.clone(), - } - } -} - -type RouteFuture = LocalBoxFuture< - 'static, - Result, ()>, ->; - -#[pin_project::pin_project] -pub struct CreateRouteService { - #[pin] - fut: RouteFuture, - guards: Rc>>, -} - -impl Future for CreateRouteService { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - match this.fut.poll(cx)? { - Poll::Ready(service) => Poll::Ready(Ok(RouteService { - service, - guards: this.guards.clone(), - })), - Poll::Pending => Poll::Pending, - } - } -} - -pub struct RouteService { - service: BoxedRouteService, - guards: Rc>>, -} - -impl RouteService { - pub fn check(&self, req: &mut ServiceRequest) -> bool { - for f in self.guards.iter() { - if !f.check(req.head()) { - return false; - } - } - true - } -} - -impl Service for RouteService { - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Future = 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 { - self.service.call(req).boxed_local() - } -} - -impl Route { - /// Add method guard to the route. - /// - /// ```rust - /// # use actix_web::*; - /// # fn main() { - /// App::new().service(web::resource("/path").route( - /// web::get() - /// .method(http::Method::CONNECT) - /// .guard(guard::Header("content-type", "text/plain")) - /// .to(|req: HttpRequest| HttpResponse::Ok())) - /// ); - /// # } - /// ``` - pub fn method(mut self, method: Method) -> Self { - Rc::get_mut(&mut self.guards) - .unwrap() - .push(Box::new(guard::Method(method))); - self - } - - /// Add guard to the route. - /// - /// ```rust - /// # use actix_web::*; - /// # fn main() { - /// App::new().service(web::resource("/path").route( - /// web::route() - /// .guard(guard::Get()) - /// .guard(guard::Header("content-type", "text/plain")) - /// .to(|req: HttpRequest| HttpResponse::Ok())) - /// ); - /// # } - /// ``` - pub fn guard(mut self, f: F) -> Self { - Rc::get_mut(&mut self.guards).unwrap().push(Box::new(f)); - self - } - - /// Set handler function, use request extractors for parameters. - /// - /// ```rust - /// use actix_web::{web, http, App}; - /// use serde_derive::Deserialize; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// async fn index(info: web::Path) -> String { - /// format!("Welcome {}!", info.username) - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/{username}/index.html") // <- define path parameters - /// .route(web::get().to(index)) // <- register handler - /// ); - /// } - /// ``` - /// - /// It is possible to use multiple extractors for one handler function. - /// - /// ```rust - /// # use std::collections::HashMap; - /// # use serde_derive::Deserialize; - /// use actix_web::{web, App}; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// /// extract path info using serde - /// async fn index(path: web::Path, query: web::Query>, body: web::Json) -> String { - /// format!("Welcome {}!", path.username) - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::resource("/{username}/index.html") // <- define path parameters - /// .route(web::get().to(index)) - /// ); - /// } - /// ``` - pub fn to(mut self, handler: F) -> Self - where - F: Factory, - T: FromRequest + 'static, - R: Future + 'static, - U: Responder + 'static, - { - self.service = - Box::new(RouteNewService::new(Extract::new(Handler::new(handler)))); - self - } -} - -struct RouteNewService -where - T: ServiceFactory, -{ - service: T, -} - -impl RouteNewService -where - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = (Error, ServiceRequest), - >, - T::Future: 'static, - T::Service: 'static, - ::Future: 'static, -{ - pub fn new(service: T) -> Self { - RouteNewService { service } - } -} - -impl ServiceFactory for RouteNewService -where - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = (Error, ServiceRequest), - >, - T::Future: 'static, - T::Service: 'static, - ::Future: 'static, -{ - type Config = (); - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Service = BoxedRouteService; - type Future = LocalBoxFuture<'static, Result>; - - fn new_service(&self, _: ()) -> Self::Future { - self.service - .new_service(()) - .map(|result| match result { - Ok(service) => { - let service: BoxedRouteService<_, _> = - Box::new(RouteServiceWrapper { service }); - Ok(service) - } - Err(_) => Err(()), - }) - .boxed_local() - } -} - -struct RouteServiceWrapper { - service: T, -} - -impl Service for RouteServiceWrapper -where - T::Future: 'static, - T: Service< - Request = ServiceRequest, - Response = ServiceResponse, - Error = (Error, ServiceRequest), - >, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Future = LocalBoxFuture<'static, Result>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx).map_err(|(e, _)| e) - } - - fn call(&mut self, req: ServiceRequest) -> Self::Future { - // let mut fut = self.service.call(req); - self.service - .call(req) - .map(|res| match res { - Ok(res) => Ok(res), - Err((err, req)) => Ok(req.error_response(err)), - }) - .boxed_local() - - // match fut.poll() { - // Poll::Ready(Ok(res)) => Either::Left(ok(res)), - // Poll::Ready(Err((e, req))) => Either::Left(ok(req.error_response(e))), - // Poll::Pending => Either::Right(Box::new(fut.then(|res| match res { - // Ok(res) => Ok(res), - // Err((err, req)) => Ok(req.error_response(err)), - // }))), - // } - } -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use actix_rt::time::delay_for; - use bytes::Bytes; - use serde_derive::Serialize; - - use crate::http::{Method, StatusCode}; - use crate::test::{call_service, init_service, read_body, TestRequest}; - use crate::{error, web, App, HttpResponse}; - - #[derive(Serialize, PartialEq, Debug)] - struct MyObject { - name: String, - } - - #[actix_rt::test] - async fn test_route() { - let mut srv = init_service( - App::new() - .service( - web::resource("/test") - .route(web::get().to(|| HttpResponse::Ok())) - .route(web::put().to(|| { - async { - Err::(error::ErrorBadRequest("err")) - } - })) - .route(web::post().to(|| { - async { - delay_for(Duration::from_millis(100)).await; - HttpResponse::Created() - } - })) - .route(web::delete().to(|| { - async { - delay_for(Duration::from_millis(100)).await; - Err::(error::ErrorBadRequest("err")) - } - })), - ) - .service(web::resource("/json").route(web::get().to(|| { - async { - delay_for(Duration::from_millis(25)).await; - web::Json(MyObject { - name: "test".to_string(), - }) - } - }))), - ) - .await; - - let req = TestRequest::with_uri("/test") - .method(Method::GET) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/test") - .method(Method::POST) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::CREATED); - - let req = TestRequest::with_uri("/test") - .method(Method::PUT) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/test") - .method(Method::DELETE) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/test") - .method(Method::HEAD) - .to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let req = TestRequest::with_uri("/json").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - - let body = read_body(resp).await; - assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); - } -} diff --git a/src/scope.rs b/src/scope.rs deleted file mode 100644 index 18e775e61..000000000 --- a/src/scope.rs +++ /dev/null @@ -1,1225 +0,0 @@ -use std::cell::RefCell; -use std::fmt; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; - -use actix_http::{Extensions, Response}; -use actix_router::{ResourceDef, ResourceInfo, Router}; -use actix_service::boxed::{self, BoxService, BoxServiceFactory}; -use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, -}; -use futures::future::{ok, Either, Future, LocalBoxFuture, Ready}; - -use crate::config::ServiceConfig; -use crate::data::Data; -use crate::dev::{AppService, HttpServiceFactory}; -use crate::error::Error; -use crate::guard::Guard; -use crate::resource::Resource; -use crate::rmap::ResourceMap; -use crate::route::Route; -use crate::service::{ - AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, -}; - -type Guards = Vec>; -type HttpService = BoxService; -type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; -type BoxedResponse = LocalBoxFuture<'static, Result>; - -/// Resources scope. -/// -/// Scope is a set of resources with common root path. -/// Scopes collect multiple paths under a common path prefix. -/// Scope path can contain variable path segments as resources. -/// Scope prefix is always complete path segment, i.e `/app` would -/// be converted to a `/app/` and it would not match `/app` path. -/// -/// You can get variable path segments from `HttpRequest::match_info()`. -/// `Path` extractor also is able to extract scope level variable segments. -/// -/// ```rust -/// use actix_web::{web, App, HttpResponse}; -/// -/// fn main() { -/// let app = App::new().service( -/// web::scope("/{project_id}/") -/// .service(web::resource("/path1").to(|| async { HttpResponse::Ok() })) -/// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok()))) -/// .service(web::resource("/path3").route(web::head().to(|| HttpResponse::MethodNotAllowed()))) -/// ); -/// } -/// ``` -/// -/// In the above example three routes get registered: -/// * /{project_id}/path1 - reponds to all http method -/// * /{project_id}/path2 - `GET` requests -/// * /{project_id}/path3 - `HEAD` requests -/// -pub struct Scope { - endpoint: T, - rdef: String, - data: Option, - services: Vec>, - guards: Vec>, - default: Rc>>>, - external: Vec, - factory_ref: Rc>>, -} - -impl Scope { - /// Create a new scope - pub fn new(path: &str) -> Scope { - let fref = Rc::new(RefCell::new(None)); - Scope { - endpoint: ScopeEndpoint::new(fref.clone()), - rdef: path.to_string(), - data: None, - guards: Vec::new(), - services: Vec::new(), - default: Rc::new(RefCell::new(None)), - external: Vec::new(), - factory_ref: fref, - } - } -} - -impl Scope -where - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ - /// Add match guard to a scope. - /// - /// ```rust - /// use actix_web::{web, guard, App, HttpRequest, HttpResponse}; - /// - /// async fn index(data: web::Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::scope("/app") - /// .guard(guard::Header("content-type", "text/plain")) - /// .route("/test1", web::get().to(index)) - /// .route("/test2", web::post().to(|r: HttpRequest| { - /// HttpResponse::MethodNotAllowed() - /// })) - /// ); - /// } - /// ``` - pub fn guard(mut self, guard: G) -> Self { - self.guards.push(Box::new(guard)); - self - } - - /// Set or override application data. Application data could be accessed - /// by using `Data` extractor where `T` is data type. - /// - /// ```rust - /// use std::cell::Cell; - /// use actix_web::{web, App, HttpResponse, Responder}; - /// - /// struct MyData { - /// counter: Cell, - /// } - /// - /// async fn index(data: web::Data) -> impl Responder { - /// data.counter.set(data.counter.get() + 1); - /// HttpResponse::Ok() - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::scope("/app") - /// .data(MyData{ counter: Cell::new(0) }) - /// .service( - /// web::resource("/index.html").route( - /// web::get().to(index))) - /// ); - /// } - /// ``` - pub fn data(self, data: U) -> Self { - self.app_data(Data::new(data)) - } - - /// Set or override application data. - /// - /// This method overrides data stored with [`App::app_data()`](#method.app_data) - pub fn app_data(mut self, data: U) -> Self { - if self.data.is_none() { - self.data = Some(Extensions::new()); - } - self.data.as_mut().unwrap().insert(data); - self - } - - /// Run external configuration as part of the scope building - /// process - /// - /// This function is useful for moving parts of configuration to a - /// different module or even library. For example, - /// some of the resource's configuration could be moved to different module. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{web, middleware, App, HttpResponse}; - /// - /// // this function could be located in different module - /// fn config(cfg: &mut web::ServiceConfig) { - /// cfg.service(web::resource("/test") - /// .route(web::get().to(|| HttpResponse::Ok())) - /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) - /// ); - /// } - /// - /// fn main() { - /// let app = App::new() - /// .wrap(middleware::Logger::default()) - /// .service( - /// web::scope("/api") - /// .configure(config) - /// ) - /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); - /// } - /// ``` - pub fn configure(mut self, f: F) -> Self - where - F: FnOnce(&mut ServiceConfig), - { - let mut cfg = ServiceConfig::new(); - f(&mut cfg); - self.services.extend(cfg.services); - self.external.extend(cfg.external); - - if !cfg.data.is_empty() { - let mut data = self.data.unwrap_or_else(Extensions::new); - - for value in cfg.data.iter() { - value.create(&mut data); - } - - self.data = Some(data); - } - self - } - - /// Register http service. - /// - /// This is similar to `App's` service registration. - /// - /// Actix web provides several services implementations: - /// - /// * *Resource* is an entry in resource table which corresponds to requested URL. - /// * *Scope* is a set of resources with common root path. - /// * "StaticFiles" is a service for static files support - /// - /// ```rust - /// use actix_web::{web, App, HttpRequest}; - /// - /// struct AppState; - /// - /// async fn index(req: HttpRequest) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::scope("/app").service( - /// web::scope("/v1") - /// .service(web::resource("/test1").to(index))) - /// ); - /// } - /// ``` - pub fn service(mut self, factory: F) -> Self - where - F: HttpServiceFactory + 'static, - { - self.services - .push(Box::new(ServiceFactoryWrapper::new(factory))); - self - } - - /// Configure route for a specific path. - /// - /// This is a simplified version of the `Scope::service()` method. - /// This method can be called multiple times, in that case - /// multiple resources with one route would be registered for same resource path. - /// - /// ```rust - /// use actix_web::{web, App, HttpResponse}; - /// - /// async fn index(data: web::Path<(String, String)>) -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::scope("/app") - /// .route("/test1", web::get().to(index)) - /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())) - /// ); - /// } - /// ``` - pub fn route(self, path: &str, mut route: Route) -> Self { - self.service( - Resource::new(path) - .add_guards(route.take_guards()) - .route(route), - ) - } - - /// Default service to be used if no matching route could be found. - /// - /// If default resource is not registered, app's default resource is being used. - pub fn default_service(mut self, f: F) -> Self - where - F: IntoServiceFactory, - U: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - > + 'static, - U::InitError: fmt::Debug, - { - // create and configure default resource - self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory( - f.into_factory().map_init_err(|e| { - log::error!("Can not construct default service: {:?}", e) - }), - ))))); - - self - } - - /// Registers middleware, in the form of a middleware component (type), - /// that runs during inbound processing in the request - /// lifecycle (request -> response), modifying request as - /// necessary, across all requests managed by the *Scope*. Scope-level - /// middleware is more limited in what it can modify, relative to Route or - /// Application level middleware, in that Scope-level middleware can not modify - /// ServiceResponse. - /// - /// Use middleware when you need to read or modify *every* request in some way. - pub fn wrap( - self, - mw: M, - ) -> Scope< - impl ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - > - where - M: Transform< - T::Service, - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - { - Scope { - endpoint: apply(mw, self.endpoint), - rdef: self.rdef, - data: self.data, - guards: self.guards, - services: self.services, - default: self.default, - external: self.external, - factory_ref: self.factory_ref, - } - } - - /// Registers middleware, in the form of a closure, that runs during inbound - /// processing in the request lifecycle (request -> response), modifying - /// request as necessary, across all requests managed by the *Scope*. - /// Scope-level middleware is more limited in what it can modify, relative - /// to Route or Application level middleware, in that Scope-level middleware - /// can not modify ServiceResponse. - /// - /// ```rust - /// use actix_service::Service; - /// use actix_web::{web, App}; - /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; - /// - /// async fn index() -> &'static str { - /// "Welcome!" - /// } - /// - /// fn main() { - /// let app = App::new().service( - /// web::scope("/app") - /// .wrap_fn(|req, srv| { - /// let fut = srv.call(req); - /// async { - /// let mut res = fut.await?; - /// res.headers_mut().insert( - /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), - /// ); - /// Ok(res) - /// } - /// }) - /// .route("/index.html", web::get().to(index))); - /// } - /// ``` - pub fn wrap_fn( - self, - mw: F, - ) -> Scope< - impl ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - > - where - F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, - R: Future>, - { - Scope { - endpoint: apply_fn_factory(self.endpoint, mw), - rdef: self.rdef, - data: self.data, - guards: self.guards, - services: self.services, - default: self.default, - external: self.external, - factory_ref: self.factory_ref, - } - } -} - -impl HttpServiceFactory for Scope -where - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - > + 'static, -{ - fn register(mut self, config: &mut AppService) { - // update default resource if needed - if self.default.borrow().is_none() { - *self.default.borrow_mut() = Some(config.default_service()); - } - - // register nested services - let mut cfg = config.clone_config(); - self.services - .into_iter() - .for_each(|mut srv| srv.register(&mut cfg)); - - let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef)); - - // external resources - for mut rdef in std::mem::replace(&mut self.external, Vec::new()) { - rmap.add(&mut rdef, None); - } - - // custom app data storage - if let Some(ref mut ext) = self.data { - config.set_service_data(ext); - } - - // complete scope pipeline creation - *self.factory_ref.borrow_mut() = Some(ScopeFactory { - data: self.data.take().map(Rc::new), - default: self.default.clone(), - services: Rc::new( - cfg.into_services() - .1 - .into_iter() - .map(|(mut rdef, srv, guards, nested)| { - rmap.add(&mut rdef, nested); - (rdef, srv, RefCell::new(guards)) - }) - .collect(), - ), - }); - - // get guards - let guards = if self.guards.is_empty() { - None - } else { - Some(self.guards) - }; - - // register final service - config.register_service( - ResourceDef::root_prefix(&self.rdef), - guards, - self.endpoint, - Some(Rc::new(rmap)), - ) - } -} - -pub struct ScopeFactory { - data: Option>, - services: Rc>)>>, - default: Rc>>>, -} - -impl ServiceFactory for ScopeFactory { - type Config = (); - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Service = ScopeService; - type Future = ScopeFactoryResponse; - - fn new_service(&self, _: ()) -> Self::Future { - let default_fut = if let Some(ref default) = *self.default.borrow() { - Some(default.new_service(())) - } else { - None - }; - - ScopeFactoryResponse { - fut: self - .services - .iter() - .map(|(path, service, guards)| { - CreateScopeServiceItem::Future( - Some(path.clone()), - guards.borrow_mut().take(), - service.new_service(()), - ) - }) - .collect(), - default: None, - data: self.data.clone(), - default_fut, - } - } -} - -/// Create scope service -#[doc(hidden)] -#[pin_project::pin_project] -pub struct ScopeFactoryResponse { - fut: Vec, - data: Option>, - default: Option, - default_fut: Option>>, -} - -type HttpServiceFut = LocalBoxFuture<'static, Result>; - -enum CreateScopeServiceItem { - Future(Option, Option, HttpServiceFut), - Service(ResourceDef, Option, HttpService), -} - -impl Future for ScopeFactoryResponse { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut done = true; - - if let Some(ref mut fut) = self.default_fut { - match Pin::new(fut).poll(cx)? { - Poll::Ready(default) => self.default = Some(default), - Poll::Pending => done = false, - } - } - - // poll http services - for item in &mut self.fut { - let res = match item { - CreateScopeServiceItem::Future( - ref mut path, - ref mut guards, - ref mut fut, - ) => match Pin::new(fut).poll(cx)? { - Poll::Ready(service) => { - Some((path.take().unwrap(), guards.take(), service)) - } - Poll::Pending => { - done = false; - None - } - }, - CreateScopeServiceItem::Service(_, _, _) => continue, - }; - - if let Some((path, guards, service)) = res { - *item = CreateScopeServiceItem::Service(path, guards, service); - } - } - - if done { - let router = self - .fut - .drain(..) - .fold(Router::build(), |mut router, item| { - match item { - CreateScopeServiceItem::Service(path, guards, service) => { - router.rdef(path, service).2 = guards; - } - CreateScopeServiceItem::Future(_, _, _) => unreachable!(), - } - router - }); - Poll::Ready(Ok(ScopeService { - data: self.data.clone(), - router: router.finish(), - default: self.default.take(), - _ready: None, - })) - } else { - Poll::Pending - } - } -} - -pub struct ScopeService { - data: Option>, - router: Router>>, - default: Option, - _ready: Option<(ServiceRequest, ResourceInfo)>, -} - -impl Service for ScopeService { - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type Future = Either>>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - let res = self.router.recognize_mut_checked(&mut req, |req, guards| { - if let Some(ref guards) = guards { - for f in guards { - if !f.check(req.head()) { - return false; - } - } - } - true - }); - - if let Some((srv, _info)) = res { - if let Some(ref data) = self.data { - req.set_data_container(data.clone()); - } - Either::Left(srv.call(req)) - } else if let Some(ref mut default) = self.default { - Either::Left(default.call(req)) - } else { - let req = req.into_parts().0; - Either::Right(ok(ServiceResponse::new(req, Response::NotFound().finish()))) - } - } -} - -#[doc(hidden)] -pub struct ScopeEndpoint { - factory: Rc>>, -} - -impl ScopeEndpoint { - fn new(factory: Rc>>) -> Self { - ScopeEndpoint { factory } - } -} - -impl ServiceFactory for ScopeEndpoint { - type Config = (); - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = Error; - type InitError = (); - type Service = ScopeService; - type Future = ScopeFactoryResponse; - - fn new_service(&self, _: ()) -> Self::Future { - self.factory.borrow_mut().as_mut().unwrap().new_service(()) - } -} - -#[cfg(test)] -mod tests { - use actix_service::Service; - use bytes::Bytes; - use futures::future::ok; - - use crate::dev::{Body, ResponseBody}; - use crate::http::{header, HeaderValue, Method, StatusCode}; - use crate::middleware::DefaultHeaders; - use crate::service::ServiceRequest; - use crate::test::{call_service, init_service, read_body, TestRequest}; - use crate::{guard, web, App, HttpRequest, HttpResponse}; - - #[actix_rt::test] - async fn test_scope() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::resource("/path1").to(|| HttpResponse::Ok())), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_scope_root() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::resource("").to(|| HttpResponse::Ok())) - .service(web::resource("/").to(|| HttpResponse::Created())), - ), - ) - .await; - - let req = TestRequest::with_uri("/app").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - } - - #[actix_rt::test] - async fn test_scope_root2() { - let mut srv = init_service(App::new().service( - web::scope("/app/").service(web::resource("").to(|| HttpResponse::Ok())), - )) - .await; - - let req = TestRequest::with_uri("/app").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_scope_root3() { - let mut srv = init_service(App::new().service( - web::scope("/app/").service(web::resource("/").to(|| HttpResponse::Ok())), - )) - .await; - - let req = TestRequest::with_uri("/app").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - } - - #[actix_rt::test] - async fn test_scope_route() { - let mut srv = init_service( - App::new().service( - web::scope("app") - .route("/path1", web::get().to(|| HttpResponse::Ok())) - .route("/path1", web::delete().to(|| HttpResponse::Ok())), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - } - - #[actix_rt::test] - async fn test_scope_route_without_leading_slash() { - let mut srv = init_service( - App::new().service( - web::scope("app").service( - web::resource("path1") - .route(web::get().to(|| HttpResponse::Ok())) - .route(web::delete().to(|| HttpResponse::Ok())), - ), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::DELETE) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[actix_rt::test] - async fn test_scope_guard() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .guard(guard::Get()) - .service(web::resource("/path1").to(|| HttpResponse::Ok())), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/path1") - .method(Method::POST) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/path1") - .method(Method::GET) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_scope_variable_segment() { - let mut srv = - init_service(App::new().service(web::scope("/ab-{project}").service( - web::resource("/path1").to(|r: HttpRequest| { - async move { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) - } - }), - ))) - .await; - - let req = TestRequest::with_uri("/ab-project1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project1")); - } - _ => panic!(), - } - - let req = TestRequest::with_uri("/aa-project1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - } - - #[actix_rt::test] - async fn test_nested_scope() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::scope("/t1").service( - web::resource("/path1").to(|| HttpResponse::Created()), - )), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/t1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - } - - #[actix_rt::test] - async fn test_nested_scope_no_slash() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::scope("t1").service( - web::resource("/path1").to(|| HttpResponse::Created()), - )), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/t1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - } - - #[actix_rt::test] - async fn test_nested_scope_root() { - let mut srv = init_service( - App::new().service( - web::scope("/app").service( - web::scope("/t1") - .service(web::resource("").to(|| HttpResponse::Ok())) - .service(web::resource("/").to(|| HttpResponse::Created())), - ), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/t1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - - let req = TestRequest::with_uri("/app/t1/").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - } - - #[actix_rt::test] - async fn test_nested_scope_filter() { - let mut srv = init_service( - App::new().service( - web::scope("/app").service( - web::scope("/t1") - .guard(guard::Get()) - .service(web::resource("/path1").to(|| HttpResponse::Ok())), - ), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::POST) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - let req = TestRequest::with_uri("/app/t1/path1") - .method(Method::GET) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_nested_scope_with_variable_segment() { - let mut srv = init_service(App::new().service(web::scope("/app").service( - web::scope("/{project_id}").service(web::resource("/path1").to( - |r: HttpRequest| { - async move { - HttpResponse::Created() - .body(format!("project: {}", &r.match_info()["project_id"])) - } - }, - )), - ))) - .await; - - let req = TestRequest::with_uri("/app/project_1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: project_1")); - } - _ => panic!(), - } - } - - #[actix_rt::test] - async fn test_nested2_scope_with_variable_segment() { - let mut srv = init_service(App::new().service(web::scope("/app").service( - web::scope("/{project}").service(web::scope("/{id}").service( - web::resource("/path1").to(|r: HttpRequest| { - async move { - HttpResponse::Created().body(format!( - "project: {} - {}", - &r.match_info()["project"], - &r.match_info()["id"], - )) - } - }), - )), - ))) - .await; - - let req = TestRequest::with_uri("/app/test/1/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::CREATED); - - match resp.response().body() { - ResponseBody::Body(Body::Bytes(ref b)) => { - let bytes: Bytes = b.clone().into(); - assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); - } - _ => panic!(), - } - - let req = TestRequest::with_uri("/app/test/1/path2").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - } - - #[actix_rt::test] - async fn test_default_resource() { - let mut srv = init_service( - App::new().service( - web::scope("/app") - .service(web::resource("/path1").to(|| HttpResponse::Ok())) - .default_service(|r: ServiceRequest| { - ok(r.into_response(HttpResponse::BadRequest())) - }), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/path2").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/path2").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - } - - #[actix_rt::test] - async fn test_default_resource_propagation() { - let mut srv = init_service( - App::new() - .service(web::scope("/app1").default_service( - web::resource("").to(|| HttpResponse::BadRequest()), - )) - .service(web::scope("/app2")) - .default_service(|r: ServiceRequest| { - ok(r.into_response(HttpResponse::MethodNotAllowed())) - }), - ) - .await; - - let req = TestRequest::with_uri("/non-exist").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - - let req = TestRequest::with_uri("/app1/non-exist").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let req = TestRequest::with_uri("/app2/non-exist").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - } - - #[actix_rt::test] - async fn test_middleware() { - let mut srv = - init_service( - App::new().service( - web::scope("app") - .wrap(DefaultHeaders::new().header( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - )) - .service( - web::resource("/test") - .route(web::get().to(|| HttpResponse::Ok())), - ), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - } - - #[actix_rt::test] - async fn test_middleware_fn() { - let mut srv = init_service( - App::new().service( - web::scope("app") - .wrap_fn(|req, srv| { - let fut = srv.call(req); - async move { - let mut res = fut.await?; - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); - Ok(res) - } - }) - .route("/test", web::get().to(|| HttpResponse::Ok())), - ), - ) - .await; - - let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - HeaderValue::from_static("0001") - ); - } - - #[actix_rt::test] - async fn test_override_data() { - let mut srv = init_service(App::new().data(1usize).service( - web::scope("app").data(10usize).route( - "/t", - web::get().to(|data: web::Data| { - assert_eq!(**data, 10); - let _ = data.clone(); - HttpResponse::Ok() - }), - ), - )) - .await; - - let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_override_app_data() { - let mut srv = init_service(App::new().app_data(web::Data::new(1usize)).service( - web::scope("app").app_data(web::Data::new(10usize)).route( - "/t", - web::get().to(|data: web::Data| { - assert_eq!(**data, 10); - let _ = data.clone(); - HttpResponse::Ok() - }), - ), - )) - .await; - - let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_scope_config() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.route("/path1", web::get().to(|| HttpResponse::Ok())); - }))) - .await; - - let req = TestRequest::with_uri("/app/path1").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_scope_config_2() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.service(web::scope("/v1").configure(|s| { - s.route("/", web::get().to(|| HttpResponse::Ok())); - })); - }))) - .await; - - let req = TestRequest::with_uri("/app/v1/").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - } - - #[actix_rt::test] - async fn test_url_for_external() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.service(web::scope("/v1").configure(|s| { - s.external_resource( - "youtube", - "https://youtube.com/watch/{video_id}", - ); - s.route( - "/", - web::get().to(|req: HttpRequest| { - async move { - HttpResponse::Ok().body(format!( - "{}", - req.url_for("youtube", &["xxxxxx"]) - .unwrap() - .as_str() - )) - } - }), - ); - })); - }))) - .await; - - let req = TestRequest::with_uri("/app/v1/").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp).await; - assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]); - } - - #[actix_rt::test] - async fn test_url_for_nested() { - let mut srv = init_service(App::new().service(web::scope("/a").service( - web::scope("/b").service(web::resource("/c/{stuff}").name("c").route( - web::get().to(|req: HttpRequest| { - async move { - HttpResponse::Ok() - .body(format!("{}", req.url_for("c", &["12345"]).unwrap())) - } - }), - )), - ))) - .await; - - let req = TestRequest::with_uri("/a/b/c/test").to_request(); - let resp = call_service(&mut srv, req).await; - assert_eq!(resp.status(), StatusCode::OK); - let body = read_body(resp).await; - assert_eq!( - body, - Bytes::from_static(b"http://localhost:8080/a/b/c/12345") - ); - } -} diff --git a/src/server.rs b/src/server.rs deleted file mode 100644 index 11cfbb6bc..000000000 --- a/src/server.rs +++ /dev/null @@ -1,595 +0,0 @@ -use std::marker::PhantomData; -use std::sync::{Arc, Mutex}; -use std::{fmt, io, net}; - -use actix_http::{body::MessageBody, Error, HttpService, KeepAlive, Request, Response}; -use actix_server::{Server, ServerBuilder}; -use actix_service::{map_config, IntoServiceFactory, Service, ServiceFactory}; - -use net2::TcpBuilder; - -#[cfg(unix)] -use actix_http::Protocol; -#[cfg(unix)] -use actix_service::pipeline_factory; -#[cfg(unix)] -use futures::future::ok; - -#[cfg(feature = "openssl")] -use actix_tls::openssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; -#[cfg(feature = "rustls")] -use actix_tls::rustls::ServerConfig as RustlsServerConfig; - -use crate::config::AppConfig; - -struct Socket { - scheme: &'static str, - addr: net::SocketAddr, -} - -struct Config { - host: Option, - keep_alive: KeepAlive, - client_timeout: u64, - client_shutdown: u64, -} - -/// An HTTP Server. -/// -/// Create new http server with application factory. -/// -/// ```rust,no_run -/// use actix_web::{web, App, HttpResponse, HttpServer}; -/// -/// #[actix_rt::main] -/// async fn main() -> std::io::Result<()> { -/// HttpServer::new( -/// || App::new() -/// .service(web::resource("/").to(|| HttpResponse::Ok()))) -/// .bind("127.0.0.1:59090")? -/// .run() -/// .await -/// } -/// ``` -pub struct HttpServer -where - F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory, - S::Error: Into, - S::InitError: fmt::Debug, - S::Response: Into>, - B: MessageBody, -{ - pub(super) factory: F, - config: Arc>, - backlog: i32, - sockets: Vec, - builder: ServerBuilder, - _t: PhantomData<(S, B)>, -} - -impl HttpServer -where - F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, -{ - /// Create new http server with application factory - pub fn new(factory: F) -> Self { - HttpServer { - factory, - config: Arc::new(Mutex::new(Config { - host: None, - keep_alive: KeepAlive::Timeout(5), - client_timeout: 5000, - client_shutdown: 5000, - })), - backlog: 1024, - sockets: Vec::new(), - builder: ServerBuilder::default(), - _t: PhantomData, - } - } - - /// Set number of workers to start. - /// - /// By default http server uses number of available logical cpu as threads - /// count. - pub fn workers(mut self, num: usize) -> Self { - self.builder = self.builder.workers(num); - self - } - - /// Set the maximum number of pending connections. - /// - /// This refers to the number of clients that can be waiting to be served. - /// Exceeding this number results in the client getting an error when - /// attempting to connect. It should only affect servers under significant - /// load. - /// - /// Generally set in the 64-2048 range. Default value is 2048. - /// - /// This method should be called before `bind()` method call. - pub fn backlog(mut self, backlog: i32) -> Self { - self.backlog = backlog; - self.builder = self.builder.backlog(backlog); - self - } - - /// Sets the maximum per-worker number of concurrent connections. - /// - /// All socket listeners will stop accepting connections when this limit is reached - /// for each worker. - /// - /// By default max connections is set to a 25k. - pub fn maxconn(mut self, num: usize) -> Self { - self.builder = self.builder.maxconn(num); - self - } - - /// Sets the maximum per-worker concurrent connection establish process. - /// - /// All listeners will stop accepting connections when this limit is reached. It - /// can be used to limit the global SSL CPU usage. - /// - /// By default max connections is set to a 256. - pub fn maxconnrate(self, num: usize) -> Self { - actix_tls::max_concurrent_ssl_connect(num); - self - } - - /// Set server keep-alive setting. - /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(self, val: T) -> Self { - self.config.lock().unwrap().keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_timeout(self, val: u64) -> Self { - self.config.lock().unwrap().client_timeout = val; - self - } - - /// Set server connection shutdown timeout in milliseconds. - /// - /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete - /// within this time, the request is dropped. - /// - /// To disable timeout set value to 0. - /// - /// By default client timeout is set to 5000 milliseconds. - pub fn client_shutdown(self, val: u64) -> Self { - self.config.lock().unwrap().client_shutdown = val; - self - } - - /// Set server host name. - /// - /// Host name is used by application router as a hostname for url generation. - /// Check [ConnectionInfo](./dev/struct.ConnectionInfo.html#method.host) - /// documentation for more information. - /// - /// By default host name is set to a "localhost" value. - pub fn server_hostname>(self, val: T) -> Self { - self.config.lock().unwrap().host = Some(val.as_ref().to_owned()); - self - } - - /// Stop actix system. - pub fn system_exit(mut self) -> Self { - self.builder = self.builder.system_exit(); - self - } - - /// Disable signal handling - pub fn disable_signals(mut self) -> Self { - self.builder = self.builder.disable_signals(); - self - } - - /// Timeout for graceful workers shutdown. - /// - /// After receiving a stop signal, workers have this much time to finish - /// serving requests. Workers still alive after the timeout are force - /// dropped. - /// - /// By default shutdown timeout sets to 30 seconds. - pub fn shutdown_timeout(mut self, sec: u64) -> Self { - self.builder = self.builder.shutdown_timeout(sec); - self - } - - /// Get addresses of bound sockets. - pub fn addrs(&self) -> Vec { - self.sockets.iter().map(|s| s.addr).collect() - } - - /// Get addresses of bound sockets and the scheme for it. - /// - /// This is useful when the server is bound from different sources - /// with some sockets listening on http and some listening on https - /// and the user should be presented with an enumeration of which - /// socket requires which protocol. - pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { - self.sockets.iter().map(|s| (s.addr, s.scheme)).collect() - } - - /// Use listener for accepting incoming connection requests - /// - /// HttpServer does not change any configuration for TcpListener, - /// it needs to be configured before passing it to listen() method. - pub fn listen(mut self, lst: net::TcpListener) -> io::Result { - let cfg = self.config.clone(); - let factory = self.factory.clone(); - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - addr, - scheme: "http", - }); - - self.builder = self.builder.listen( - format!("actix-web-service-{}", addr), - lst, - move || { - let c = cfg.lock().unwrap(); - let cfg = AppConfig::new( - false, - addr, - c.host.clone().unwrap_or_else(|| format!("{}", addr)), - ); - - HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .local_addr(addr) - .finish(map_config(factory(), move |_| cfg.clone())) - .tcp() - }, - )?; - Ok(self) - } - - #[cfg(feature = "openssl")] - /// Use listener for accepting incoming tls connection requests - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_openssl( - self, - lst: net::TcpListener, - builder: SslAcceptorBuilder, - ) -> io::Result { - self.listen_ssl_inner(lst, openssl_acceptor(builder)?) - } - - #[cfg(feature = "openssl")] - fn listen_ssl_inner( - mut self, - lst: net::TcpListener, - acceptor: SslAcceptor, - ) -> io::Result { - let factory = self.factory.clone(); - let cfg = self.config.clone(); - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - addr, - scheme: "https", - }); - - self.builder = self.builder.listen( - format!("actix-web-service-{}", addr), - lst, - move || { - let c = cfg.lock().unwrap(); - let cfg = AppConfig::new( - true, - addr, - c.host.clone().unwrap_or_else(|| format!("{}", addr)), - ); - HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown) - .finish(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }, - )?; - Ok(self) - } - - #[cfg(feature = "rustls")] - /// Use listener for accepting incoming tls connection requests - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_rustls( - self, - lst: net::TcpListener, - config: RustlsServerConfig, - ) -> io::Result { - self.listen_rustls_inner(lst, config) - } - - #[cfg(feature = "rustls")] - fn listen_rustls_inner( - mut self, - lst: net::TcpListener, - config: RustlsServerConfig, - ) -> io::Result { - let factory = self.factory.clone(); - let cfg = self.config.clone(); - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - addr, - scheme: "https", - }); - - self.builder = self.builder.listen( - format!("actix-web-service-{}", addr), - lst, - move || { - let c = cfg.lock().unwrap(); - let cfg = AppConfig::new( - true, - addr, - c.host.clone().unwrap_or_else(|| format!("{}", addr)), - ); - HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown) - .finish(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }, - )?; - Ok(self) - } - - /// The socket address to bind - /// - /// To bind multiple addresses this method can be called multiple times. - pub fn bind(mut self, addr: A) -> io::Result { - let sockets = self.bind2(addr)?; - - for lst in sockets { - self = self.listen(lst)?; - } - - Ok(self) - } - - fn bind2( - &self, - addr: A, - ) -> io::Result> { - let mut err = None; - let mut succ = false; - let mut sockets = Vec::new(); - for addr in addr.to_socket_addrs()? { - match create_tcp_listener(addr, self.backlog) { - Ok(lst) => { - succ = true; - sockets.push(lst); - } - Err(e) => err = Some(e), - } - } - - if !succ { - if let Some(e) = err.take() { - Err(e) - } else { - Err(io::Error::new( - io::ErrorKind::Other, - "Can not bind to address.", - )) - } - } else { - Ok(sockets) - } - } - - #[cfg(feature = "openssl")] - /// Start listening for incoming tls connections. - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_openssl( - mut self, - addr: A, - builder: SslAcceptorBuilder, - ) -> io::Result - where - A: net::ToSocketAddrs, - { - let sockets = self.bind2(addr)?; - let acceptor = openssl_acceptor(builder)?; - - for lst in sockets { - self = self.listen_ssl_inner(lst, acceptor.clone())?; - } - - Ok(self) - } - - #[cfg(feature = "rustls")] - /// Start listening for incoming tls connections. - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_rustls( - mut self, - addr: A, - config: RustlsServerConfig, - ) -> io::Result { - let sockets = self.bind2(addr)?; - for lst in sockets { - self = self.listen_rustls_inner(lst, config.clone())?; - } - Ok(self) - } - - #[cfg(unix)] - /// Start listening for unix domain connections on existing listener. - /// - /// This method is available with `uds` feature. - pub fn listen_uds( - mut self, - lst: std::os::unix::net::UnixListener, - ) -> io::Result { - use actix_rt::net::UnixStream; - - let cfg = self.config.clone(); - let factory = self.factory.clone(); - let socket_addr = net::SocketAddr::new( - net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), - 8080, - ); - self.sockets.push(Socket { - scheme: "http", - addr: socket_addr, - }); - - let addr = format!("actix-web-service-{:?}", lst.local_addr()?); - - self.builder = self.builder.listen_uds(addr, lst, move || { - let c = cfg.lock().unwrap(); - let config = AppConfig::new( - false, - socket_addr, - c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), - ); - pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then( - HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .finish(map_config(factory(), move |_| config.clone())), - ) - })?; - Ok(self) - } - - #[cfg(unix)] - /// Start listening for incoming unix domain connections. - /// - /// This method is available with `uds` feature. - pub fn bind_uds(mut self, addr: A) -> io::Result - where - A: AsRef, - { - use actix_rt::net::UnixStream; - - let cfg = self.config.clone(); - let factory = self.factory.clone(); - let socket_addr = net::SocketAddr::new( - net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), - 8080, - ); - self.sockets.push(Socket { - scheme: "http", - addr: socket_addr, - }); - - self.builder = self.builder.bind_uds( - format!("actix-web-service-{:?}", addr.as_ref()), - addr, - move || { - let c = cfg.lock().unwrap(); - let config = AppConfig::new( - false, - socket_addr, - c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), - ); - pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))) - .and_then( - HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .finish(map_config(factory(), move |_| config.clone())), - ) - }, - )?; - Ok(self) - } -} - -impl HttpServer -where - F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory, - S::Error: Into, - S::InitError: fmt::Debug, - S::Response: Into>, - S::Service: 'static, - B: MessageBody, -{ - /// Start listening for incoming connections. - /// - /// This method starts number of http workers in separate threads. - /// For each address this method starts separate thread which does - /// `accept()` in a loop. - /// - /// This methods panics if no socket address can be bound or an `Actix` system is not yet - /// configured. - /// - /// ```rust,no_run - /// use std::io; - /// use actix_web::{web, App, HttpResponse, HttpServer}; - /// - /// #[actix_rt::main] - /// async fn main() -> io::Result<()> { - /// HttpServer::new(|| App::new().service(web::resource("/").to(|| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0")? - /// .run() - /// .await - /// } - /// ``` - pub fn run(self) -> Server { - self.builder.start() - } -} - -fn create_tcp_listener( - addr: net::SocketAddr, - backlog: i32, -) -> io::Result { - let builder = match addr { - net::SocketAddr::V4(_) => TcpBuilder::new_v4()?, - net::SocketAddr::V6(_) => TcpBuilder::new_v6()?, - }; - builder.reuse_address(true)?; - builder.bind(addr)?; - Ok(builder.listen(backlog)?) -} - -#[cfg(feature = "openssl")] -/// Configure `SslAcceptorBuilder` with custom server flags. -fn openssl_acceptor(mut builder: SslAcceptorBuilder) -> io::Result { - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - const H11: &[u8] = b"\x08http/1.1"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else if protos.windows(9).any(|window| window == H11) { - Ok(b"http/1.1") - } else { - Err(AlpnError::NOACK) - } - }); - builder.set_alpn_protos(b"\x08http/1.1\x02h2")?; - - Ok(builder.build()) -} diff --git a/src/service.rs b/src/service.rs deleted file mode 100644 index e51be9964..000000000 --- a/src/service.rs +++ /dev/null @@ -1,602 +0,0 @@ -use std::cell::{Ref, RefMut}; -use std::rc::Rc; -use std::{fmt, net}; - -use actix_http::body::{Body, MessageBody, ResponseBody}; -use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; -use actix_http::{ - Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, - ResponseHead, -}; -use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url}; -use actix_service::{IntoServiceFactory, ServiceFactory}; - -use crate::config::{AppConfig, AppService}; -use crate::data::Data; -use crate::dev::insert_slash; -use crate::guard::Guard; -use crate::info::ConnectionInfo; -use crate::request::HttpRequest; -use crate::rmap::ResourceMap; - -pub trait HttpServiceFactory { - fn register(self, config: &mut AppService); -} - -pub(crate) trait AppServiceFactory { - fn register(&mut self, config: &mut AppService); -} - -pub(crate) struct ServiceFactoryWrapper { - factory: Option, -} - -impl ServiceFactoryWrapper { - pub fn new(factory: T) -> Self { - Self { - factory: Some(factory), - } - } -} - -impl AppServiceFactory for ServiceFactoryWrapper -where - T: HttpServiceFactory, -{ - fn register(&mut self, config: &mut AppService) { - if let Some(item) = self.factory.take() { - item.register(config) - } - } -} - -/// An service http request -/// -/// ServiceRequest allows mutable access to request's internal structures -pub struct ServiceRequest(HttpRequest); - -impl ServiceRequest { - /// Construct service request - pub(crate) fn new(req: HttpRequest) -> Self { - ServiceRequest(req) - } - - /// Deconstruct request into parts - pub fn into_parts(mut self) -> (HttpRequest, Payload) { - let pl = Rc::get_mut(&mut (self.0).0).unwrap().payload.take(); - (self.0, pl) - } - - /// Construct request from parts. - /// - /// `ServiceRequest` can be re-constructed only if `req` hasnt been cloned. - pub fn from_parts( - mut req: HttpRequest, - pl: Payload, - ) -> Result { - if Rc::strong_count(&req.0) == 1 && Rc::weak_count(&req.0) == 0 { - Rc::get_mut(&mut req.0).unwrap().payload = pl; - Ok(ServiceRequest(req)) - } else { - Err((req, pl)) - } - } - - /// Construct request from request. - /// - /// `HttpRequest` implements `Clone` trait via `Rc` type. `ServiceRequest` - /// can be re-constructed only if rc's strong pointers count eq 1 and - /// weak pointers count is 0. - pub fn from_request(req: HttpRequest) -> Result { - if Rc::strong_count(&req.0) == 1 && Rc::weak_count(&req.0) == 0 { - Ok(ServiceRequest(req)) - } else { - Err(req) - } - } - - /// Create service response - #[inline] - pub fn into_response>>(self, res: R) -> ServiceResponse { - ServiceResponse::new(self.0, res.into()) - } - - /// Create service response for error - #[inline] - pub fn error_response>(self, err: E) -> ServiceResponse { - let res: Response = err.into().into(); - ServiceResponse::new(self.0, res.into_body()) - } - - /// This method returns reference to the request head - #[inline] - pub fn head(&self) -> &RequestHead { - &self.0.head() - } - - /// This method returns reference to the request head - #[inline] - pub fn head_mut(&mut self) -> &mut RequestHead { - self.0.head_mut() - } - - /// 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 - } - - #[inline] - /// Returns mutable request's headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().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 { - "" - } - } - - /// Peer socket address - /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. - /// - /// To get client connection information `ConnectionInfo` should be used. - #[inline] - pub fn peer_addr(&self) -> Option { - self.head().peer_addr - } - - /// Get *ConnectionInfo* for the current request. - #[inline] - pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { - ConnectionInfo::get(self.head(), &*self.app_config()) - } - - /// 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.0.match_info() - } - - #[inline] - /// Get a mutable reference to the Path parameters. - pub fn match_info_mut(&mut self) -> &mut Path { - self.0.match_info_mut() - } - - #[inline] - /// Get a reference to a `ResourceMap` of current application. - pub fn resource_map(&self) -> &ResourceMap { - self.0.resource_map() - } - - /// Service configuration - #[inline] - pub fn app_config(&self) -> &AppConfig { - self.0.app_config() - } - - /// Get an application data stored with `App::data()` method during - /// application configuration. - pub fn app_data(&self) -> Option> { - if let Some(st) = (self.0).0.app_data.get::>() { - Some(st.clone()) - } else { - None - } - } - - /// Set request payload. - pub fn set_payload(&mut self, payload: Payload) { - Rc::get_mut(&mut (self.0).0).unwrap().payload = payload; - } - - #[doc(hidden)] - /// Set new app data container - pub fn set_data_container(&mut self, extensions: Rc) { - Rc::get_mut(&mut (self.0).0).unwrap().app_data = extensions; - } -} - -impl Resource for ServiceRequest { - fn resource_path(&mut self) -> &mut Path { - self.match_info_mut() - } -} - -impl HttpMessage for ServiceRequest { - type Stream = PayloadStream; - - #[inline] - /// Returns Request's headers. - fn headers(&self) -> &HeaderMap { - &self.head().headers - } - - /// Request extensions - #[inline] - fn extensions(&self) -> Ref<'_, Extensions> { - self.0.extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.0.extensions_mut() - } - - #[inline] - fn take_payload(&mut self) -> Payload { - Rc::get_mut(&mut (self.0).0).unwrap().payload.take() - } -} - -impl fmt::Debug for ServiceRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!( - f, - "\nServiceRequest {:?} {}:{}", - self.head().version, - self.head().method, - self.path() - )?; - if !self.query_string().is_empty() { - writeln!(f, " query: ?{:?}", self.query_string())?; - } - if !self.match_info().is_empty() { - writeln!(f, " params: {:?}", self.match_info())?; - } - writeln!(f, " headers:")?; - for (key, val) in self.headers().iter() { - writeln!(f, " {:?}: {:?}", key, val)?; - } - Ok(()) - } -} - -pub struct ServiceResponse { - request: HttpRequest, - response: Response, -} - -impl ServiceResponse { - /// Create service response instance - pub fn new(request: HttpRequest, response: Response) -> Self { - ServiceResponse { request, response } - } - - /// Create service response from the error - pub fn from_err>(err: E, request: HttpRequest) -> Self { - let e: Error = err.into(); - let res: Response = e.into(); - ServiceResponse { - request, - response: res.into_body(), - } - } - - /// Create service response for error - #[inline] - pub fn error_response>(self, err: E) -> Self { - Self::from_err(err, self.request) - } - - /// Create service response - #[inline] - pub fn into_response(self, response: Response) -> ServiceResponse { - ServiceResponse::new(self.request, response) - } - - /// Get reference to original request - #[inline] - pub fn request(&self) -> &HttpRequest { - &self.request - } - - /// Get reference to response - #[inline] - pub fn response(&self) -> &Response { - &self.response - } - - /// Get mutable reference to response - #[inline] - pub fn response_mut(&mut self) -> &mut Response { - &mut self.response - } - - /// Get the response status code - #[inline] - pub fn status(&self) -> StatusCode { - self.response.status() - } - - #[inline] - /// Returns response's headers. - pub fn headers(&self) -> &HeaderMap { - self.response.headers() - } - - #[inline] - /// Returns mutable response's headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - self.response.headers_mut() - } - - /// Execute closure and in case of error convert it to response. - pub fn checked_expr(mut self, f: F) -> Self - where - F: FnOnce(&mut Self) -> Result<(), E>, - E: Into, - { - match f(&mut self) { - Ok(_) => self, - Err(err) => { - let res: Response = err.into().into(); - ServiceResponse::new(self.request, res.into_body()) - } - } - } - - /// Extract response body - pub fn take_body(&mut self) -> ResponseBody { - self.response.take_body() - } -} - -impl ServiceResponse { - /// Set a new body - pub fn map_body(self, f: F) -> ServiceResponse - where - F: FnOnce(&mut ResponseHead, ResponseBody) -> ResponseBody, - { - let response = self.response.map_body(f); - - ServiceResponse { - response, - request: self.request, - } - } -} - -impl Into> for ServiceResponse { - fn into(self) -> Response { - self.response - } -} - -impl fmt::Debug for ServiceResponse { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let res = writeln!( - f, - "\nServiceResponse {:?} {}{}", - self.response.head().version, - self.response.head().status, - self.response.head().reason.unwrap_or(""), - ); - let _ = writeln!(f, " headers:"); - for (key, val) in self.response.head().headers.iter() { - let _ = writeln!(f, " {:?}: {:?}", key, val); - } - let _ = writeln!(f, " body: {:?}", self.response.body().size()); - res - } -} - -pub struct WebService { - rdef: Vec, - name: Option, - guards: Vec>, -} - -impl WebService { - /// Create new `WebService` instance. - pub fn new(path: T) -> Self { - WebService { - rdef: path.patterns(), - name: None, - guards: Vec::new(), - } - } - - /// Set service name. - /// - /// Name is used for url generation. - pub fn name(mut self, name: &str) -> Self { - self.name = Some(name.to_string()); - self - } - - /// Add match guard to a web service. - /// - /// ```rust - /// use actix_web::{web, guard, dev, App, Error, HttpResponse}; - /// - /// async fn index(req: dev::ServiceRequest) -> Result { - /// Ok(req.into_response(HttpResponse::Ok().finish())) - /// } - /// - /// fn main() { - /// let app = App::new() - /// .service( - /// web::service("/app") - /// .guard(guard::Header("content-type", "text/plain")) - /// .finish(index) - /// ); - /// } - /// ``` - pub fn guard(mut self, guard: G) -> Self { - self.guards.push(Box::new(guard)); - self - } - - /// Set a service factory implementation and generate web service. - pub fn finish(self, service: F) -> impl HttpServiceFactory - where - F: IntoServiceFactory, - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - > + 'static, - { - WebServiceImpl { - srv: service.into_factory(), - rdef: self.rdef, - name: self.name, - guards: self.guards, - } - } -} - -struct WebServiceImpl { - srv: T, - rdef: Vec, - name: Option, - guards: Vec>, -} - -impl HttpServiceFactory for WebServiceImpl -where - T: ServiceFactory< - Config = (), - Request = ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - > + 'static, -{ - fn register(mut self, config: &mut AppService) { - let guards = if self.guards.is_empty() { - None - } else { - Some(std::mem::replace(&mut self.guards, Vec::new())) - }; - - let mut rdef = if config.is_root() || !self.rdef.is_empty() { - ResourceDef::new(insert_slash(self.rdef)) - } else { - ResourceDef::new(self.rdef) - }; - if let Some(ref name) = self.name { - *rdef.name_mut() = name.clone(); - } - config.register_service(rdef, guards, self.srv, None) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test::{init_service, TestRequest}; - use crate::{guard, http, web, App, HttpResponse}; - use actix_service::Service; - use futures::future::ok; - - #[test] - fn test_service_request() { - let req = TestRequest::default().to_srv_request(); - let (r, pl) = req.into_parts(); - assert!(ServiceRequest::from_parts(r, pl).is_ok()); - - let req = TestRequest::default().to_srv_request(); - let (r, pl) = req.into_parts(); - let _r2 = r.clone(); - assert!(ServiceRequest::from_parts(r, pl).is_err()); - - let req = TestRequest::default().to_srv_request(); - let (r, _pl) = req.into_parts(); - assert!(ServiceRequest::from_request(r).is_ok()); - - let req = TestRequest::default().to_srv_request(); - let (r, _pl) = req.into_parts(); - let _r2 = r.clone(); - assert!(ServiceRequest::from_request(r).is_err()); - } - - #[actix_rt::test] - async fn test_service() { - let mut srv = init_service( - App::new().service(web::service("/test").name("test").finish( - |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())), - )), - ) - .await; - let req = TestRequest::with_uri("/test").to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), http::StatusCode::OK); - - let mut srv = init_service( - App::new().service(web::service("/test").guard(guard::Get()).finish( - |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())), - )), - ) - .await; - let req = TestRequest::with_uri("/test") - .method(http::Method::PUT) - .to_request(); - let resp = srv.call(req).await.unwrap(); - assert_eq!(resp.status(), http::StatusCode::NOT_FOUND); - } - - #[test] - fn test_fmt_debug() { - let req = TestRequest::get() - .uri("/index.html?test=1") - .header("x-test", "111") - .to_srv_request(); - let s = format!("{:?}", req); - assert!(s.contains("ServiceRequest")); - assert!(s.contains("test=1")); - assert!(s.contains("x-test")); - - let res = HttpResponse::Ok().header("x-test", "111").finish(); - let res = TestRequest::post() - .uri("/index.html?test=1") - .to_srv_response(res); - - let s = format!("{:?}", res); - assert!(s.contains("ServiceResponse")); - assert!(s.contains("x-test")); - } -} diff --git a/src/test.rs b/src/test.rs deleted file mode 100644 index 956980530..000000000 --- a/src/test.rs +++ /dev/null @@ -1,1208 +0,0 @@ -//! Various helpers for Actix applications to use during testing. -use std::convert::TryFrom; -use std::net::SocketAddr; -use std::rc::Rc; -use std::sync::mpsc; -use std::{fmt, net, thread, time}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_http::http::header::{ContentType, Header, HeaderName, IntoHeaderValue}; -use actix_http::http::{Error as HttpError, Method, StatusCode, Uri, Version}; -use actix_http::test::TestRequest as HttpTestRequest; -use actix_http::{cookie::Cookie, ws, Extensions, HttpService, Request}; -use actix_router::{Path, ResourceDef, Url}; -use actix_rt::{time::delay_for, System}; -use actix_service::{ - map_config, IntoService, IntoServiceFactory, Service, ServiceFactory, -}; -use awc::error::PayloadError; -use awc::{Client, ClientRequest, ClientResponse, Connector}; -use bytes::{Bytes, BytesMut}; -use futures::future::ok; -use futures::stream::{Stream, StreamExt}; -use net2::TcpBuilder; -use serde::de::DeserializeOwned; -use serde::Serialize; -use serde_json; - -pub use actix_http::test::TestBuffer; - -use crate::config::AppConfig; -use crate::data::Data; -use crate::dev::{Body, MessageBody, Payload, Server}; -use crate::request::HttpRequestPool; -use crate::rmap::ResourceMap; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::{Error, HttpRequest, HttpResponse}; - -/// Create service that always responds with `HttpResponse::Ok()` -pub fn ok_service( -) -> impl Service, Error = Error> -{ - default_service(StatusCode::OK) -} - -/// Create service that responds with response with specified status code -pub fn default_service( - status_code: StatusCode, -) -> impl Service, Error = Error> -{ - (move |req: ServiceRequest| { - ok(req.into_response(HttpResponse::build(status_code).finish())) - }) - .into_service() -} - -/// This method accepts application builder instance, and constructs -/// service. -/// -/// ```rust -/// use actix_service::Service; -/// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; -/// -/// #[actix_rt::test] -/// async fn test_init_service() { -/// let mut app = test::init_service( -/// App::new() -/// .service(web::resource("/test").to(|| async { HttpResponse::Ok() })) -/// ).await; -/// -/// // Create request object -/// let req = test::TestRequest::with_uri("/test").to_request(); -/// -/// // Execute application -/// let resp = app.call(req).await.unwrap(); -/// assert_eq!(resp.status(), StatusCode::OK); -/// } -/// ``` -pub async fn init_service( - app: R, -) -> impl Service, Error = E> -where - R: IntoServiceFactory, - S: ServiceFactory< - Config = AppConfig, - Request = Request, - Response = ServiceResponse, - Error = E, - >, - S::InitError: std::fmt::Debug, -{ - let srv = app.into_factory(); - srv.new_service(AppConfig::default()).await.unwrap() -} - -/// Calls service and waits for response future completion. -/// -/// ```rust -/// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; -/// -/// #[actix_rt::test] -/// async fn test_response() { -/// let mut app = test::init_service( -/// App::new() -/// .service(web::resource("/test").to(|| async { -/// HttpResponse::Ok() -/// })) -/// ).await; -/// -/// // Create request object -/// let req = test::TestRequest::with_uri("/test").to_request(); -/// -/// // Call application -/// let resp = test::call_service(&mut app, req).await; -/// assert_eq!(resp.status(), StatusCode::OK); -/// } -/// ``` -pub async fn call_service(app: &mut S, req: R) -> S::Response -where - S: Service, Error = E>, - E: std::fmt::Debug, -{ - app.call(req).await.unwrap() -} - -/// Helper function that returns a response body of a TestRequest -/// -/// ```rust -/// use actix_web::{test, web, App, HttpResponse, http::header}; -/// use bytes::Bytes; -/// -/// #[actix_rt::test] -/// async fn test_index() { -/// let mut app = test::init_service( -/// App::new().service( -/// web::resource("/index.html") -/// .route(web::post().to(|| async { -/// HttpResponse::Ok().body("welcome!") -/// }))) -/// ).await; -/// -/// let req = test::TestRequest::post() -/// .uri("/index.html") -/// .header(header::CONTENT_TYPE, "application/json") -/// .to_request(); -/// -/// let result = test::read_response(&mut app, req).await; -/// assert_eq!(result, Bytes::from_static(b"welcome!")); -/// } -/// ``` -pub async fn read_response(app: &mut S, req: Request) -> Bytes -where - S: Service, Error = Error>, - B: MessageBody, -{ - let mut resp = app - .call(req) - .await - .unwrap_or_else(|_| panic!("read_response failed at application call")); - - let mut body = resp.take_body(); - let mut bytes = BytesMut::new(); - while let Some(item) = body.next().await { - bytes.extend_from_slice(&item.unwrap()); - } - bytes.freeze() -} - -/// Helper function that returns a response body of a ServiceResponse. -/// -/// ```rust -/// use actix_web::{test, web, App, HttpResponse, http::header}; -/// use bytes::Bytes; -/// -/// #[actix_rt::test] -/// async fn test_index() { -/// let mut app = test::init_service( -/// App::new().service( -/// web::resource("/index.html") -/// .route(web::post().to(|| async { -/// HttpResponse::Ok().body("welcome!") -/// }))) -/// ).await; -/// -/// let req = test::TestRequest::post() -/// .uri("/index.html") -/// .header(header::CONTENT_TYPE, "application/json") -/// .to_request(); -/// -/// let resp = test::call_service(&mut app, req).await; -/// let result = test::read_body(resp); -/// assert_eq!(result, Bytes::from_static(b"welcome!")); -/// } -/// ``` -pub async fn read_body(mut res: ServiceResponse) -> Bytes -where - B: MessageBody, -{ - let mut body = res.take_body(); - let mut bytes = BytesMut::new(); - while let Some(item) = body.next().await { - bytes.extend_from_slice(&item.unwrap()); - } - bytes.freeze() -} - -pub async fn load_stream(mut stream: S) -> Result -where - S: Stream> + Unpin, -{ - let mut data = BytesMut::new(); - while let Some(item) = stream.next().await { - data.extend_from_slice(&item?); - } - Ok(data.freeze()) -} - -/// Helper function that returns a deserialized response body of a TestRequest -/// -/// ```rust -/// use actix_web::{App, test, web, HttpResponse, http::header}; -/// use serde::{Serialize, Deserialize}; -/// -/// #[derive(Serialize, Deserialize)] -/// pub struct Person { -/// id: String, -/// name: String -/// } -/// -/// #[actix_rt::test] -/// async fn test_add_person() { -/// let mut app = test::init_service( -/// App::new().service( -/// web::resource("/people") -/// .route(web::post().to(|person: web::Json| async { -/// HttpResponse::Ok() -/// .json(person.into_inner())}) -/// )) -/// ).await; -/// -/// let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); -/// -/// let req = test::TestRequest::post() -/// .uri("/people") -/// .header(header::CONTENT_TYPE, "application/json") -/// .set_payload(payload) -/// .to_request(); -/// -/// let result: Person = test::read_response_json(&mut app, req).await; -/// } -/// ``` -pub async fn read_response_json(app: &mut S, req: Request) -> T -where - S: Service, Error = Error>, - B: MessageBody, - T: DeserializeOwned, -{ - let body = read_response(app, req).await; - - serde_json::from_slice(&body) - .unwrap_or_else(|_| panic!("read_response_json failed during deserialization")) -} - -/// Test `Request` builder. -/// -/// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. -/// You can generate various types of request via TestRequest's methods: -/// * `TestRequest::to_request` creates `actix_http::Request` instance. -/// * `TestRequest::to_srv_request` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters. -/// * `TestRequest::to_srv_response` creates `ServiceResponse` instance. -/// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. -/// -/// ```rust -/// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; -/// use actix_web::http::{header, StatusCode}; -/// -/// async fn index(req: HttpRequest) -> HttpResponse { -/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { -/// HttpResponse::Ok().into() -/// } else { -/// HttpResponse::BadRequest().into() -/// } -/// } -/// -/// #[test] -/// fn test_index() { -/// let req = test::TestRequest::with_header("content-type", "text/plain") -/// .to_http_request(); -/// -/// let resp = index(req).await.unwrap(); -/// assert_eq!(resp.status(), StatusCode::OK); -/// -/// let req = test::TestRequest::default().to_http_request(); -/// let resp = index(req).await.unwrap(); -/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); -/// } -/// ``` -pub struct TestRequest { - req: HttpTestRequest, - rmap: ResourceMap, - config: AppConfig, - path: Path, - peer_addr: Option, - app_data: Extensions, -} - -impl Default for TestRequest { - fn default() -> TestRequest { - TestRequest { - req: HttpTestRequest::default(), - rmap: ResourceMap::new(ResourceDef::new("")), - config: AppConfig::default(), - path: Path::new(Url::new(Uri::default())), - peer_addr: None, - app_data: Extensions::new(), - } - } -} - -#[allow(clippy::wrong_self_convention)] -impl TestRequest { - /// Create TestRequest and set request uri - pub fn with_uri(path: &str) -> TestRequest { - TestRequest::default().uri(path) - } - - /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestRequest { - TestRequest::default().set(hdr) - } - - /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> TestRequest - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - TestRequest::default().header(key, value) - } - - /// Create TestRequest and set method to `Method::GET` - pub fn get() -> TestRequest { - TestRequest::default().method(Method::GET) - } - - /// Create TestRequest and set method to `Method::POST` - pub fn post() -> TestRequest { - TestRequest::default().method(Method::POST) - } - - /// Create TestRequest and set method to `Method::PUT` - pub fn put() -> TestRequest { - TestRequest::default().method(Method::PUT) - } - - /// Create TestRequest and set method to `Method::PATCH` - pub fn patch() -> TestRequest { - TestRequest::default().method(Method::PATCH) - } - - /// Create TestRequest and set method to `Method::DELETE` - pub fn delete() -> TestRequest { - TestRequest::default().method(Method::DELETE) - } - - /// Set HTTP version of this request - pub fn version(mut self, ver: Version) -> Self { - self.req.version(ver); - self - } - - /// Set HTTP method of this request - pub fn method(mut self, meth: Method) -> Self { - self.req.method(meth); - self - } - - /// Set HTTP Uri of this request - pub fn uri(mut self, path: &str) -> Self { - self.req.uri(path); - self - } - - /// Set a header - pub fn set(mut self, hdr: H) -> Self { - self.req.set(hdr); - self - } - - /// Set a header - pub fn header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - self.req.header(key, value); - self - } - - /// Set cookie for this request - pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { - self.req.cookie(cookie); - self - } - - /// Set request path pattern parameter - pub fn param(mut self, name: &'static str, value: &'static str) -> Self { - self.path.add_static(name, value); - self - } - - /// Set peer addr - pub fn peer_addr(mut self, addr: SocketAddr) -> Self { - self.peer_addr = Some(addr); - self - } - - /// Set request payload - pub fn set_payload>(mut self, data: B) -> Self { - self.req.set_payload(data); - self - } - - /// Serialize `data` to a URL encoded form and set it as the request payload. The `Content-Type` - /// header is set to `application/x-www-form-urlencoded`. - pub fn set_form(mut self, data: &T) -> Self { - let bytes = serde_urlencoded::to_string(data) - .expect("Failed to serialize test data as a urlencoded form"); - self.req.set_payload(bytes); - self.req.set(ContentType::form_url_encoded()); - self - } - - /// Serialize `data` to JSON and set it as the request payload. The `Content-Type` header is - /// set to `application/json`. - pub fn set_json(mut self, data: &T) -> Self { - let bytes = - serde_json::to_string(data).expect("Failed to serialize test data to json"); - self.req.set_payload(bytes); - self.req.set(ContentType::json()); - self - } - - /// Set application data. This is equivalent of `App::data()` method - /// for testing purpose. - pub fn data(mut self, data: T) -> Self { - self.app_data.insert(Data::new(data)); - self - } - - /// Set application data. This is equivalent of `App::app_data()` method - /// for testing purpose. - pub fn app_data(mut self, data: T) -> Self { - self.app_data.insert(data); - self - } - - #[cfg(test)] - /// Set request config - pub(crate) fn rmap(mut self, rmap: ResourceMap) -> Self { - self.rmap = rmap; - self - } - - /// Complete request creation and generate `Request` instance - pub fn to_request(mut self) -> Request { - let mut req = self.req.finish(); - req.head_mut().peer_addr = self.peer_addr; - req - } - - /// Complete request creation and generate `ServiceRequest` instance - pub fn to_srv_request(mut self) -> ServiceRequest { - let (mut head, payload) = self.req.finish().into_parts(); - head.peer_addr = self.peer_addr; - self.path.get_mut().update(&head.uri); - - ServiceRequest::new(HttpRequest::new( - self.path, - head, - payload, - Rc::new(self.rmap), - self.config.clone(), - Rc::new(self.app_data), - HttpRequestPool::create(), - )) - } - - /// Complete request creation and generate `ServiceResponse` instance - pub fn to_srv_response(self, res: HttpResponse) -> ServiceResponse { - self.to_srv_request().into_response(res) - } - - /// Complete request creation and generate `HttpRequest` instance - pub fn to_http_request(mut self) -> HttpRequest { - let (mut head, payload) = self.req.finish().into_parts(); - head.peer_addr = self.peer_addr; - self.path.get_mut().update(&head.uri); - - HttpRequest::new( - self.path, - head, - payload, - Rc::new(self.rmap), - self.config.clone(), - Rc::new(self.app_data), - HttpRequestPool::create(), - ) - } - - /// Complete request creation and generate `HttpRequest` and `Payload` instances - pub fn to_http_parts(mut self) -> (HttpRequest, Payload) { - let (mut head, payload) = self.req.finish().into_parts(); - head.peer_addr = self.peer_addr; - self.path.get_mut().update(&head.uri); - - let req = HttpRequest::new( - self.path, - head, - Payload::None, - Rc::new(self.rmap), - self.config.clone(), - Rc::new(self.app_data), - HttpRequestPool::create(), - ); - - (req, payload) - } -} - -/// Start test server with default configuration -/// -/// Test server is very simple server that simplify process of writing -/// integration tests cases for actix web applications. -/// -/// # Examples -/// -/// ```rust -/// use actix_web::{web, test, App, HttpResponse, Error}; -/// -/// async fn my_handler() -> Result { -/// Ok(HttpResponse::Ok().into()) -/// } -/// -/// #[actix_rt::test] -/// async fn test_example() { -/// let mut srv = test::start( -/// || App::new().service( -/// web::resource("/").to(my_handler)) -/// ); -/// -/// let req = srv.get("/"); -/// let response = req.send().await.unwrap(); -/// assert!(response.status().is_success()); -/// } -/// ``` -pub fn start(factory: F) -> TestServer -where - F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory + 'static, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, -{ - start_with(TestServerConfig::default(), factory) -} - -/// Start test server with custom configuration -/// -/// Test server could be configured in different ways, for details check -/// `TestServerConfig` docs. -/// -/// # Examples -/// -/// ```rust -/// use actix_web::{web, test, App, HttpResponse, Error}; -/// -/// async fn my_handler() -> Result { -/// Ok(HttpResponse::Ok().into()) -/// } -/// -/// #[actix_rt::test] -/// async fn test_example() { -/// let mut srv = test::start_with(test::config().h1(), || -/// App::new().service(web::resource("/").to(my_handler)) -/// ); -/// -/// let req = srv.get("/"); -/// let response = req.send().await.unwrap(); -/// assert!(response.status().is_success()); -/// } -/// ``` -pub fn start_with(cfg: TestServerConfig, factory: F) -> TestServer -where - F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory + 'static, - S::Error: Into + 'static, - S::InitError: fmt::Debug, - S::Response: Into> + 'static, - ::Future: 'static, - B: MessageBody + 'static, -{ - let (tx, rx) = mpsc::channel(); - - let ssl = match cfg.stream { - StreamType::Tcp => false, - #[cfg(feature = "openssl")] - StreamType::Openssl(_) => true, - #[cfg(feature = "rustls")] - StreamType::Rustls(_) => true, - }; - - // run server in separate thread - thread::spawn(move || { - let sys = System::new("actix-test-server"); - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let local_addr = tcp.local_addr().unwrap(); - let factory = factory.clone(); - let cfg = cfg.clone(); - let ctimeout = cfg.client_timeout; - let builder = Server::build().workers(1).disable_signals(); - - let srv = match cfg.stream { - StreamType::Tcp => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(false, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h1(map_config(factory(), move |_| cfg.clone())) - .tcp() - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(false, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h2(map_config(factory(), move |_| cfg.clone())) - .tcp() - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(false, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .finish(map_config(factory(), move |_| cfg.clone())) - .tcp() - }), - }, - #[cfg(feature = "openssl")] - StreamType::Openssl(acceptor) => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h1(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h2(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .finish(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) - }), - }, - #[cfg(feature = "rustls")] - StreamType::Rustls(config) => match cfg.tp { - HttpVer::Http1 => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h1(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }), - HttpVer::Http2 => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .h2(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }), - HttpVer::Both => builder.listen("test", tcp, move || { - let cfg = - AppConfig::new(true, local_addr, format!("{}", local_addr)); - HttpService::build() - .client_timeout(ctimeout) - .finish(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) - }), - }, - } - .unwrap() - .start(); - - tx.send((System::current(), srv, local_addr)).unwrap(); - sys.run() - }); - - let (system, server, addr) = rx.recv().unwrap(); - - let client = { - let connector = { - #[cfg(feature = "openssl")] - { - use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(30000)) - .ssl(builder.build()) - .finish() - } - #[cfg(not(feature = "openssl"))] - { - Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(30000)) - .finish() - } - }; - - Client::build().connector(connector).finish() - }; - - TestServer { - ssl, - addr, - client, - system, - server, - } -} - -#[derive(Clone)] -pub struct TestServerConfig { - tp: HttpVer, - stream: StreamType, - client_timeout: u64, -} - -#[derive(Clone)] -enum HttpVer { - Http1, - Http2, - Both, -} - -#[derive(Clone)] -enum StreamType { - Tcp, - #[cfg(feature = "openssl")] - Openssl(open_ssl::ssl::SslAcceptor), - #[cfg(feature = "rustls")] - Rustls(rust_tls::ServerConfig), -} - -impl Default for TestServerConfig { - fn default() -> Self { - TestServerConfig::new() - } -} - -/// Create default test server config -pub fn config() -> TestServerConfig { - TestServerConfig::new() -} - -impl TestServerConfig { - /// Create default server configuration - pub(crate) fn new() -> TestServerConfig { - TestServerConfig { - tp: HttpVer::Both, - stream: StreamType::Tcp, - client_timeout: 5000, - } - } - - /// Start http/1.1 server only - pub fn h1(mut self) -> Self { - self.tp = HttpVer::Http1; - self - } - - /// Start http/2 server only - pub fn h2(mut self) -> Self { - self.tp = HttpVer::Http2; - self - } - - /// Start openssl server - #[cfg(feature = "openssl")] - pub fn openssl(mut self, acceptor: open_ssl::ssl::SslAcceptor) -> Self { - self.stream = StreamType::Openssl(acceptor); - self - } - - /// Start rustls server - #[cfg(feature = "rustls")] - pub fn rustls(mut self, config: rust_tls::ServerConfig) -> Self { - self.stream = StreamType::Rustls(config); - self - } - - /// Set server client timeout in milliseconds for first request. - pub fn client_timeout(mut self, val: u64) -> Self { - self.client_timeout = val; - self - } -} - -/// Get first available unused address -pub fn unused_addr() -> net::SocketAddr { - let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = TcpBuilder::new_v4().unwrap(); - socket.bind(&addr).unwrap(); - socket.reuse_address(true).unwrap(); - let tcp = socket.to_tcp_listener().unwrap(); - tcp.local_addr().unwrap() -} - -/// Test server controller -pub struct TestServer { - addr: net::SocketAddr, - client: awc::Client, - system: actix_rt::System, - ssl: bool, - server: Server, -} - -impl TestServer { - /// Construct test server url - pub fn addr(&self) -> net::SocketAddr { - self.addr - } - - /// Construct test server url - pub fn url(&self, uri: &str) -> String { - let scheme = if self.ssl { "https" } else { "http" }; - - if uri.starts_with('/') { - format!("{}://localhost:{}{}", scheme, self.addr.port(), uri) - } else { - format!("{}://localhost:{}/{}", scheme, self.addr.port(), uri) - } - } - - /// Create `GET` request - pub fn get>(&self, path: S) -> ClientRequest { - self.client.get(self.url(path.as_ref()).as_str()) - } - - /// Create `POST` request - pub fn post>(&self, path: S) -> ClientRequest { - self.client.post(self.url(path.as_ref()).as_str()) - } - - /// Create `HEAD` request - pub fn head>(&self, path: S) -> ClientRequest { - self.client.head(self.url(path.as_ref()).as_str()) - } - - /// Create `PUT` request - pub fn put>(&self, path: S) -> ClientRequest { - self.client.put(self.url(path.as_ref()).as_str()) - } - - /// Create `PATCH` request - pub fn patch>(&self, path: S) -> ClientRequest { - self.client.patch(self.url(path.as_ref()).as_str()) - } - - /// Create `DELETE` request - pub fn delete>(&self, path: S) -> ClientRequest { - self.client.delete(self.url(path.as_ref()).as_str()) - } - - /// Create `OPTIONS` request - pub fn options>(&self, path: S) -> ClientRequest { - self.client.options(self.url(path.as_ref()).as_str()) - } - - /// Connect to test http server - pub fn request>(&self, method: Method, path: S) -> ClientRequest { - self.client.request(method, path.as_ref()) - } - - pub async fn load_body( - &mut self, - mut response: ClientResponse, - ) -> Result - where - S: Stream> + Unpin + 'static, - { - response.body().limit(10_485_760).await - } - - /// Connect to websocket server at a given path - pub async fn ws_at( - &mut self, - path: &str, - ) -> Result, awc::error::WsClientError> - { - let url = self.url(path); - let connect = self.client.ws(url).connect(); - connect.await.map(|(_, framed)| framed) - } - - /// Connect to a websocket server - pub async fn ws( - &mut self, - ) -> Result, awc::error::WsClientError> - { - self.ws_at("/").await - } - - /// Gracefully stop http server - pub async fn stop(self) { - self.server.stop(true).await; - self.system.stop(); - delay_for(time::Duration::from_millis(100)).await; - } -} - -impl Drop for TestServer { - fn drop(&mut self) { - self.system.stop() - } -} - -#[cfg(test)] -mod tests { - use actix_http::httpmessage::HttpMessage; - use futures::FutureExt; - use serde::{Deserialize, Serialize}; - use std::time::SystemTime; - - use super::*; - use crate::{http::header, web, App, HttpResponse, Responder}; - - #[actix_rt::test] - async fn test_basics() { - let req = TestRequest::with_hdr(header::ContentType::json()) - .version(Version::HTTP_2) - .set(header::Date(SystemTime::now().into())) - .param("test", "123") - .data(10u32) - .app_data(20u64) - .peer_addr("127.0.0.1:8081".parse().unwrap()) - .to_http_request(); - assert!(req.headers().contains_key(header::CONTENT_TYPE)); - assert!(req.headers().contains_key(header::DATE)); - assert_eq!( - req.head().peer_addr, - Some("127.0.0.1:8081".parse().unwrap()) - ); - assert_eq!(&req.match_info()["test"], "123"); - assert_eq!(req.version(), Version::HTTP_2); - let data = req.app_data::>().unwrap(); - assert!(req.app_data::>().is_none()); - assert_eq!(*data.get_ref(), 10); - - assert!(req.app_data::().is_none()); - let data = req.app_data::().unwrap(); - assert_eq!(*data, 20); - } - - #[actix_rt::test] - async fn test_request_methods() { - let mut app = init_service( - App::new().service( - web::resource("/index.html") - .route(web::put().to(|| async { HttpResponse::Ok().body("put!") })) - .route( - web::patch().to(|| async { HttpResponse::Ok().body("patch!") }), - ) - .route( - web::delete() - .to(|| async { HttpResponse::Ok().body("delete!") }), - ), - ), - ) - .await; - - let put_req = TestRequest::put() - .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") - .to_request(); - - let result = read_response(&mut app, put_req).await; - assert_eq!(result, Bytes::from_static(b"put!")); - - let patch_req = TestRequest::patch() - .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") - .to_request(); - - let result = read_response(&mut app, patch_req).await; - assert_eq!(result, Bytes::from_static(b"patch!")); - - let delete_req = TestRequest::delete().uri("/index.html").to_request(); - let result = read_response(&mut app, delete_req).await; - assert_eq!(result, Bytes::from_static(b"delete!")); - } - - #[actix_rt::test] - async fn test_response() { - let mut app = - init_service(App::new().service(web::resource("/index.html").route( - web::post().to(|| async { HttpResponse::Ok().body("welcome!") }), - ))) - .await; - - let req = TestRequest::post() - .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") - .to_request(); - - let result = read_response(&mut app, req).await; - assert_eq!(result, Bytes::from_static(b"welcome!")); - } - - #[derive(Serialize, Deserialize)] - pub struct Person { - id: String, - name: String, - } - - #[actix_rt::test] - async fn test_response_json() { - let mut app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Json| { - async { HttpResponse::Ok().json(person.into_inner()) } - }), - ))) - .await; - - let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); - - let req = TestRequest::post() - .uri("/people") - .header(header::CONTENT_TYPE, "application/json") - .set_payload(payload) - .to_request(); - - let result: Person = read_response_json(&mut app, req).await; - assert_eq!(&result.id, "12345"); - } - - #[actix_rt::test] - async fn test_request_response_form() { - let mut app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Form| { - async { HttpResponse::Ok().json(person.into_inner()) } - }), - ))) - .await; - - let payload = Person { - id: "12345".to_string(), - name: "User name".to_string(), - }; - - let req = TestRequest::post() - .uri("/people") - .set_form(&payload) - .to_request(); - - assert_eq!(req.content_type(), "application/x-www-form-urlencoded"); - - let result: Person = read_response_json(&mut app, req).await; - assert_eq!(&result.id, "12345"); - assert_eq!(&result.name, "User name"); - } - - #[actix_rt::test] - async fn test_request_response_json() { - let mut app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Json| { - async { HttpResponse::Ok().json(person.into_inner()) } - }), - ))) - .await; - - let payload = Person { - id: "12345".to_string(), - name: "User name".to_string(), - }; - - let req = TestRequest::post() - .uri("/people") - .set_json(&payload) - .to_request(); - - assert_eq!(req.content_type(), "application/json"); - - let result: Person = read_response_json(&mut app, req).await; - assert_eq!(&result.id, "12345"); - assert_eq!(&result.name, "User name"); - } - - #[actix_rt::test] - async fn test_async_with_block() { - async fn async_with_block() -> Result { - let res = web::block(move || Some(4usize).ok_or("wrong")).await; - - match res { - Ok(value) => Ok(HttpResponse::Ok() - .content_type("text/plain") - .body(format!("Async with block value: {}", value))), - Err(_) => panic!("Unexpected"), - } - } - - let mut app = init_service( - App::new().service(web::resource("/index.html").to(async_with_block)), - ) - .await; - - let req = TestRequest::post().uri("/index.html").to_request(); - let res = app.call(req).await.unwrap(); - assert!(res.status().is_success()); - } - - #[actix_rt::test] - async fn test_server_data() { - async fn handler(data: web::Data) -> impl Responder { - assert_eq!(**data, 10); - HttpResponse::Ok() - } - - let mut app = init_service( - App::new() - .data(10usize) - .service(web::resource("/index.html").to(handler)), - ) - .await; - - let req = TestRequest::post().uri("/index.html").to_request(); - let res = app.call(req).await.unwrap(); - assert!(res.status().is_success()); - } - - #[actix_rt::test] - async fn test_actor() { - use actix::Actor; - - struct MyActor; - - struct Num(usize); - impl actix::Message for Num { - type Result = usize; - } - impl actix::Actor for MyActor { - type Context = actix::Context; - } - impl actix::Handler for MyActor { - type Result = usize; - fn handle(&mut self, msg: Num, _: &mut Self::Context) -> Self::Result { - msg.0 - } - } - - let addr = MyActor.start(); - - let mut app = init_service(App::new().service(web::resource("/index.html").to( - move || { - addr.send(Num(1)).map(|res| match res { - Ok(res) => { - if res == 1 { - Ok(HttpResponse::Ok()) - } else { - Ok(HttpResponse::BadRequest()) - } - } - Err(err) => Err(err), - }) - }, - ))) - .await; - - let req = TestRequest::post().uri("/index.html").to_request(); - let res = app.call(req).await.unwrap(); - assert!(res.status().is_success()); - } -} diff --git a/src/types/form.rs b/src/types/form.rs deleted file mode 100644 index d917345e1..000000000 --- a/src/types/form.rs +++ /dev/null @@ -1,504 +0,0 @@ -//! Form extractor - -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{fmt, ops}; - -use actix_http::{Error, HttpMessage, Payload, Response}; -use bytes::BytesMut; -use encoding_rs::{Encoding, UTF_8}; -use futures::future::{err, ok, FutureExt, LocalBoxFuture, Ready}; -use futures::StreamExt; -use serde::de::DeserializeOwned; -use serde::Serialize; - -#[cfg(feature = "compress")] -use crate::dev::Decompress; -use crate::error::UrlencodedError; -use crate::extract::FromRequest; -use crate::http::{ - header::{ContentType, CONTENT_LENGTH}, - StatusCode, -}; -use crate::request::HttpRequest; -use crate::responder::Responder; - -/// Form data helper (`application/x-www-form-urlencoded`) -/// -/// Can be use to extract url-encoded data from the request body, -/// or send url-encoded data as the response. -/// -/// ## Extract -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**FormConfig**](struct.FormConfig.html) allows to configure extraction -/// process. -/// -/// ### Example -/// ```rust -/// use actix_web::web; -/// use serde_derive::Deserialize; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// /// Extract form data using serde. -/// /// This handler get called only if content type is *x-www-form-urlencoded* -/// /// and content of the request could be deserialized to a `FormData` struct -/// fn index(form: web::Form) -> String { -/// format!("Welcome {}!", form.username) -/// } -/// # fn main() {} -/// ``` -/// -/// ## Respond -/// -/// The `Form` type also allows you to respond with well-formed url-encoded data: -/// simply return a value of type Form where T is the type to be url-encoded. -/// The type must implement `serde::Serialize`; -/// -/// ### Example -/// ```rust -/// use actix_web::*; -/// use serde_derive::Serialize; -/// -/// #[derive(Serialize)] -/// struct SomeForm { -/// name: String, -/// age: u8 -/// } -/// -/// // Will return a 200 response with header -/// // `Content-Type: application/x-www-form-urlencoded` -/// // and body "name=actix&age=123" -/// fn index() -> web::Form { -/// web::Form(SomeForm { -/// name: "actix".into(), -/// age: 123 -/// }) -/// } -/// # fn main() {} -/// ``` -#[derive(PartialEq, Eq, PartialOrd, Ord)] -pub struct Form(pub T); - -impl Form { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl ops::Deref for Form { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl ops::DerefMut for Form { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl FromRequest for Form -where - T: DeserializeOwned + 'static, -{ - type Config = FormConfig; - type Error = Error; - type Future = LocalBoxFuture<'static, Result>; - - #[inline] - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - let req2 = req.clone(); - let (limit, err) = req - .app_data::() - .map(|c| (c.limit, c.ehandler.clone())) - .unwrap_or((16384, None)); - - UrlEncoded::new(req, payload) - .limit(limit) - .map(move |res| match res { - Err(e) => { - if let Some(err) = err { - Err((*err)(e, &req2)) - } else { - Err(e.into()) - } - } - Ok(item) => Ok(Form(item)), - }) - .boxed_local() - } -} - -impl fmt::Debug for Form { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for Form { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl Responder for Form { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - let body = match serde_urlencoded::to_string(&self.0) { - Ok(body) => body, - Err(e) => return err(e.into()), - }; - - ok(Response::build(StatusCode::OK) - .set(ContentType::form_url_encoded()) - .body(body)) - } -} - -/// Form extractor configuration -/// -/// ```rust -/// use actix_web::{web, App, FromRequest, Result}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// /// Extract form data using serde. -/// /// Custom configuration is used for this handler, max payload size is 4k -/// async fn index(form: web::Form) -> Result { -/// Ok(format!("Welcome {}!", form.username)) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html") -/// // change `Form` extractor configuration -/// .app_data( -/// web::Form::::configure(|cfg| cfg.limit(4097)) -/// ) -/// .route(web::get().to(index)) -/// ); -/// } -/// ``` -#[derive(Clone)] -pub struct FormConfig { - limit: usize, - ehandler: Option Error>>, -} - -impl FormConfig { - /// Change max size of payload. By default max size is 16Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set custom error handler - pub fn error_handler(mut self, f: F) -> Self - where - F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static, - { - self.ehandler = Some(Rc::new(f)); - self - } -} - -impl Default for FormConfig { - fn default() -> Self { - FormConfig { - limit: 16384, - ehandler: None, - } - } -} - -/// Future that resolves to a parsed urlencoded values. -/// -/// Parse `application/x-www-form-urlencoded` encoded request's body. -/// Return `UrlEncoded` future. Form can be deserialized to any type that -/// implements `Deserialize` trait from *serde*. -/// -/// Returns error: -/// -/// * content type is not `application/x-www-form-urlencoded` -/// * content-length is greater than 32k -/// -pub struct UrlEncoded { - #[cfg(feature = "compress")] - stream: Option>, - #[cfg(not(feature = "compress"))] - stream: Option, - limit: usize, - length: Option, - encoding: &'static Encoding, - err: Option, - fut: Option>>, -} - -impl UrlEncoded { - /// Create a new future to URL encode a request - pub fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded { - // check content type - if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { - return Self::err(UrlencodedError::ContentType); - } - let encoding = match req.encoding() { - Ok(enc) => enc, - Err(_) => return Self::err(UrlencodedError::ContentType), - }; - - let mut len = None; - if let Some(l) = req.headers().get(&CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } else { - return Self::err(UrlencodedError::UnknownLength); - } - } else { - return Self::err(UrlencodedError::UnknownLength); - } - }; - - #[cfg(feature = "compress")] - let payload = Decompress::from_headers(payload.take(), req.headers()); - #[cfg(not(feature = "compress"))] - let payload = payload.take(); - - UrlEncoded { - encoding, - stream: Some(payload), - limit: 32_768, - length: len, - fut: None, - err: None, - } - } - - fn err(e: UrlencodedError) -> Self { - UrlEncoded { - stream: None, - limit: 32_768, - fut: None, - err: Some(e), - length: None, - encoding: UTF_8, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for UrlEncoded -where - U: DeserializeOwned + 'static, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if let Some(ref mut fut) = self.fut { - return Pin::new(fut).poll(cx); - } - - if let Some(err) = self.err.take() { - return Poll::Ready(Err(err)); - } - - // payload size - let limit = self.limit; - if let Some(len) = self.length.take() { - if len > limit { - return Poll::Ready(Err(UrlencodedError::Overflow { size: len, limit })); - } - } - - // future - let encoding = self.encoding; - let mut stream = self.stream.take().unwrap(); - - self.fut = Some( - async move { - let mut body = BytesMut::with_capacity(8192); - - while let Some(item) = stream.next().await { - let chunk = item?; - if (body.len() + chunk.len()) > limit { - return Err(UrlencodedError::Overflow { - size: body.len() + chunk.len(), - limit, - }); - } else { - body.extend_from_slice(&chunk); - } - } - - if encoding == UTF_8 { - serde_urlencoded::from_bytes::(&body) - .map_err(|_| UrlencodedError::Parse) - } else { - let body = encoding - .decode_without_bom_handling_and_without_replacement(&body) - .map(|s| s.into_owned()) - .ok_or(UrlencodedError::Parse)?; - serde_urlencoded::from_str::(&body) - .map_err(|_| UrlencodedError::Parse) - } - } - .boxed_local(), - ); - self.poll(cx) - } -} - -#[cfg(test)] -mod tests { - use bytes::Bytes; - use serde::{Deserialize, Serialize}; - - use super::*; - use crate::http::header::{HeaderValue, CONTENT_TYPE}; - use crate::test::TestRequest; - - #[derive(Deserialize, Serialize, Debug, PartialEq)] - struct Info { - hello: String, - counter: i64, - } - - #[actix_rt::test] - async fn test_form() { - let (req, mut pl) = - TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world&counter=123")) - .to_http_parts(); - - let Form(s) = Form::::from_request(&req, &mut pl).await.unwrap(); - assert_eq!( - s, - Info { - hello: "world".into(), - counter: 123 - } - ); - } - - fn eq(err: UrlencodedError, other: UrlencodedError) -> bool { - match err { - UrlencodedError::Overflow { .. } => match other { - UrlencodedError::Overflow { .. } => true, - _ => false, - }, - UrlencodedError::UnknownLength => match other { - UrlencodedError::UnknownLength => true, - _ => false, - }, - UrlencodedError::ContentType => match other { - UrlencodedError::ContentType => true, - _ => false, - }, - _ => false, - } - } - - #[actix_rt::test] - async fn test_urlencoded_error() { - let (req, mut pl) = - TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(CONTENT_LENGTH, "xxxx") - .to_http_parts(); - let info = UrlEncoded::::new(&req, &mut pl).await; - assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength)); - - let (req, mut pl) = - TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(CONTENT_LENGTH, "1000000") - .to_http_parts(); - let info = UrlEncoded::::new(&req, &mut pl).await; - assert!(eq( - info.err().unwrap(), - UrlencodedError::Overflow { size: 0, limit: 0 } - )); - - let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain") - .header(CONTENT_LENGTH, "10") - .to_http_parts(); - let info = UrlEncoded::::new(&req, &mut pl).await; - assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); - } - - #[actix_rt::test] - async fn test_urlencoded() { - let (req, mut pl) = - TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world&counter=123")) - .to_http_parts(); - - let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); - assert_eq!( - info, - Info { - hello: "world".to_owned(), - counter: 123 - } - ); - - let (req, mut pl) = TestRequest::with_header( - CONTENT_TYPE, - "application/x-www-form-urlencoded; charset=utf-8", - ) - .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world&counter=123")) - .to_http_parts(); - - let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); - assert_eq!( - info, - Info { - hello: "world".to_owned(), - counter: 123 - } - ); - } - - #[actix_rt::test] - async fn test_responder() { - let req = TestRequest::default().to_http_request(); - - let form = Form(Info { - hello: "world".to_string(), - counter: 123, - }); - let resp = form.respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("application/x-www-form-urlencoded") - ); - - use crate::responder::tests::BodyTest; - assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123"); - } -} diff --git a/src/types/json.rs b/src/types/json.rs deleted file mode 100644 index fb00bf7a6..000000000 --- a/src/types/json.rs +++ /dev/null @@ -1,654 +0,0 @@ -//! Json extractor/responder - -use std::future::Future; -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::{fmt, ops}; - -use bytes::BytesMut; -use futures::future::{err, ok, FutureExt, LocalBoxFuture, Ready}; -use futures::StreamExt; -use serde::de::DeserializeOwned; -use serde::Serialize; -use serde_json; - -use actix_http::http::{header::CONTENT_LENGTH, StatusCode}; -use actix_http::{HttpMessage, Payload, Response}; - -#[cfg(feature = "compress")] -use crate::dev::Decompress; -use crate::error::{Error, JsonPayloadError}; -use crate::extract::FromRequest; -use crate::request::HttpRequest; -use crate::responder::Responder; - -/// Json helper -/// -/// Json can be used for two different purpose. First is for json response -/// generation and second is for extracting typed information from request's -/// payload. -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction -/// process. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body -/// async fn index(info: web::Json) -> String { -/// format!("Welcome {}!", info.username) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::post().to(index)) -/// ); -/// } -/// ``` -/// -/// The `Json` type allows you to respond with well-formed JSON data: simply -/// return a value of type Json where T is the type of a structure -/// to serialize into *JSON*. The type `T` must implement the `Serialize` -/// trait from *serde*. -/// -/// ```rust -/// use actix_web::*; -/// use serde_derive::Serialize; -/// -/// #[derive(Serialize)] -/// struct MyObj { -/// name: String, -/// } -/// -/// fn index(req: HttpRequest) -> Result> { -/// Ok(web::Json(MyObj { -/// name: req.match_info().get("name").unwrap().to_string(), -/// })) -/// } -/// # fn main() {} -/// ``` -pub struct Json(pub T); - -impl Json { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl ops::Deref for Json { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl ops::DerefMut for Json { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl fmt::Debug for Json -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Json: {:?}", self.0) - } -} - -impl fmt::Display for Json -where - T: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl Responder for Json { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - let body = match serde_json::to_string(&self.0) { - Ok(body) => body, - Err(e) => return err(e.into()), - }; - - ok(Response::build(StatusCode::OK) - .content_type("application/json") - .body(body)) - } -} - -/// Json extractor. Allow to extract typed information from request's -/// payload. -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction -/// process. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body -/// async fn index(info: web::Json) -> String { -/// format!("Welcome {}!", info.username) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::post().to(index)) -/// ); -/// } -/// ``` -impl FromRequest for Json -where - T: DeserializeOwned + 'static, -{ - type Error = Error; - type Future = LocalBoxFuture<'static, Result>; - type Config = JsonConfig; - - #[inline] - fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - let req2 = req.clone(); - let (limit, err, ctype) = req - .app_data::() - .map(|c| (c.limit, c.ehandler.clone(), c.content_type.clone())) - .unwrap_or((32768, None, None)); - - JsonBody::new(req, payload, ctype) - .limit(limit) - .map(move |res| match res { - Err(e) => { - log::debug!( - "Failed to deserialize Json from payload. \ - Request path: {}", - req2.path() - ); - if let Some(err) = err { - Err((*err)(e, &req2)) - } else { - Err(e.into()) - } - } - Ok(data) => Ok(Json(data)), - }) - .boxed_local() - } -} - -/// Json extractor configuration -/// -/// ```rust -/// use actix_web::{error, web, App, FromRequest, HttpResponse}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body, max payload size is 4kb -/// async fn index(info: web::Json) -> String { -/// format!("Welcome {}!", info.username) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html") -/// .app_data( -/// // change json extractor configuration -/// web::Json::::configure(|cfg| { -/// cfg.limit(4096) -/// .content_type(|mime| { // <- accept text/plain content type -/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN -/// }) -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// }) -/// })) -/// .route(web::post().to(index)) -/// ); -/// } -/// ``` -#[derive(Clone)] -pub struct JsonConfig { - limit: usize, - ehandler: Option Error + Send + Sync>>, - content_type: Option bool + Send + Sync>>, -} - -impl JsonConfig { - /// Change max size of payload. By default max size is 32Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set custom error handler - pub fn error_handler(mut self, f: F) -> Self - where - F: Fn(JsonPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, - { - self.ehandler = Some(Arc::new(f)); - self - } - - /// Set predicate for allowed content types - pub fn content_type(mut self, predicate: F) -> Self - where - F: Fn(mime::Mime) -> bool + Send + Sync + 'static, - { - self.content_type = Some(Arc::new(predicate)); - self - } -} - -impl Default for JsonConfig { - fn default() -> Self { - JsonConfig { - limit: 32768, - ehandler: None, - content_type: None, - } - } -} - -/// Request's payload json parser, it resolves to a deserialized `T` value. -/// This future could be used with `ServiceRequest` and `ServiceFromRequest`. -/// -/// Returns error: -/// -/// * content type is not `application/json` -/// (unless specified in [`JsonConfig`](struct.JsonConfig.html)) -/// * content length is greater than 256k -pub struct JsonBody { - limit: usize, - length: Option, - #[cfg(feature = "compress")] - stream: Option>, - #[cfg(not(feature = "compress"))] - stream: Option, - err: Option, - fut: Option>>, -} - -impl JsonBody -where - U: DeserializeOwned + 'static, -{ - /// Create `JsonBody` for request. - pub fn new( - req: &HttpRequest, - payload: &mut Payload, - ctype: Option bool + Send + Sync>>, - ) -> Self { - // check content-type - let json = if let Ok(Some(mime)) = req.mime_type() { - mime.subtype() == mime::JSON - || mime.suffix() == Some(mime::JSON) - || ctype.as_ref().map_or(false, |predicate| predicate(mime)) - } else { - false - }; - - if !json { - return JsonBody { - limit: 262_144, - length: None, - stream: None, - fut: None, - err: Some(JsonPayloadError::ContentType), - }; - } - - let len = req - .headers() - .get(&CONTENT_LENGTH) - .and_then(|l| l.to_str().ok()) - .and_then(|s| s.parse::().ok()); - - #[cfg(feature = "compress")] - let payload = Decompress::from_headers(payload.take(), req.headers()); - #[cfg(not(feature = "compress"))] - let payload = payload.take(); - - JsonBody { - limit: 262_144, - length: len, - stream: Some(payload), - fut: None, - err: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } -} - -impl Future for JsonBody -where - U: DeserializeOwned + 'static, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if let Some(ref mut fut) = self.fut { - return Pin::new(fut).poll(cx); - } - - if let Some(err) = self.err.take() { - return Poll::Ready(Err(err)); - } - - let limit = self.limit; - if let Some(len) = self.length.take() { - if len > limit { - return Poll::Ready(Err(JsonPayloadError::Overflow)); - } - } - let mut stream = self.stream.take().unwrap(); - - self.fut = Some( - async move { - let mut body = BytesMut::with_capacity(8192); - - while let Some(item) = stream.next().await { - let chunk = item?; - if (body.len() + chunk.len()) > limit { - return Err(JsonPayloadError::Overflow); - } else { - body.extend_from_slice(&chunk); - } - } - Ok(serde_json::from_slice::(&body)?) - } - .boxed_local(), - ); - - self.poll(cx) - } -} - -#[cfg(test)] -mod tests { - use bytes::Bytes; - use serde_derive::{Deserialize, Serialize}; - - use super::*; - use crate::error::InternalError; - use crate::http::header; - use crate::test::{load_stream, TestRequest}; - use crate::HttpResponse; - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct MyObject { - name: String, - } - - fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool { - match err { - JsonPayloadError::Overflow => match other { - JsonPayloadError::Overflow => true, - _ => false, - }, - JsonPayloadError::ContentType => match other { - JsonPayloadError::ContentType => true, - _ => false, - }, - _ => false, - } - } - - #[actix_rt::test] - async fn test_responder() { - let req = TestRequest::default().to_http_request(); - - let j = Json(MyObject { - name: "test".to_string(), - }); - let resp = j.respond_to(&req).await.unwrap(); - assert_eq!(resp.status(), StatusCode::OK); - assert_eq!( - resp.headers().get(header::CONTENT_TYPE).unwrap(), - header::HeaderValue::from_static("application/json") - ); - - use crate::responder::tests::BodyTest; - assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}"); - } - - #[actix_rt::test] - async fn test_custom_error_responder() { - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .app_data(JsonConfig::default().limit(10).error_handler(|err, _| { - let msg = MyObject { - name: "invalid request".to_string(), - }; - let resp = HttpResponse::BadRequest() - .body(serde_json::to_string(&msg).unwrap()); - InternalError::from_response(err, resp).into() - })) - .to_http_parts(); - - let s = Json::::from_request(&req, &mut pl).await; - let mut resp = Response::from_error(s.err().unwrap().into()); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - - let body = load_stream(resp.take_body()).await.unwrap(); - let msg: MyObject = serde_json::from_slice(&body).unwrap(); - assert_eq!(msg.name, "invalid request"); - } - - #[actix_rt::test] - async fn test_extract() { - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .to_http_parts(); - - let s = Json::::from_request(&req, &mut pl).await.unwrap(); - assert_eq!(s.name, "test"); - assert_eq!( - s.into_inner(), - MyObject { - name: "test".to_string() - } - ); - - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .app_data(JsonConfig::default().limit(10)) - .to_http_parts(); - - let s = Json::::from_request(&req, &mut pl).await; - assert!(format!("{}", s.err().unwrap()) - .contains("Json payload size is bigger than allowed")); - - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .app_data( - JsonConfig::default() - .limit(10) - .error_handler(|_, _| JsonPayloadError::ContentType.into()), - ) - .to_http_parts(); - let s = Json::::from_request(&req, &mut pl).await; - assert!(format!("{}", s.err().unwrap()).contains("Content type error")); - } - - #[actix_rt::test] - async fn test_json_body() { - let (req, mut pl) = TestRequest::default().to_http_parts(); - let json = JsonBody::::new(&req, &mut pl, None).await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/text"), - ) - .to_http_parts(); - let json = JsonBody::::new(&req, &mut pl, None).await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); - - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000"), - ) - .to_http_parts(); - - let json = JsonBody::::new(&req, &mut pl, None) - .limit(100) - .await; - assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); - - let (req, mut pl) = TestRequest::default() - .header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .to_http_parts(); - - let json = JsonBody::::new(&req, &mut pl, None).await; - assert_eq!( - json.ok().unwrap(), - MyObject { - name: "test".to_owned() - } - ); - } - - #[actix_rt::test] - async fn test_with_json_and_bad_content_type() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .app_data(JsonConfig::default().limit(4096)) - .to_http_parts(); - - let s = Json::::from_request(&req, &mut pl).await; - assert!(s.is_err()) - } - - #[actix_rt::test] - async fn test_with_json_and_good_custom_content_type() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .app_data(JsonConfig::default().content_type(|mime: mime::Mime| { - mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN - })) - .to_http_parts(); - - let s = Json::::from_request(&req, &mut pl).await; - assert!(s.is_ok()) - } - - #[actix_rt::test] - async fn test_with_json_and_bad_custom_content_type() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/html"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .app_data(JsonConfig::default().content_type(|mime: mime::Mime| { - mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN - })) - .to_http_parts(); - - let s = Json::::from_request(&req, &mut pl).await; - assert!(s.is_err()) - } -} diff --git a/src/types/mod.rs b/src/types/mod.rs deleted file mode 100644 index b32711e2a..000000000 --- a/src/types/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Helper types - -pub(crate) mod form; -pub(crate) mod json; -mod path; -pub(crate) mod payload; -mod query; -pub(crate) mod readlines; - -pub use self::form::{Form, FormConfig}; -pub use self::json::{Json, JsonConfig}; -pub use self::path::{Path, PathConfig}; -pub use self::payload::{Payload, PayloadConfig}; -pub use self::query::{Query, QueryConfig}; -pub use self::readlines::Readlines; diff --git a/src/types/path.rs b/src/types/path.rs deleted file mode 100644 index a37cb8f12..000000000 --- a/src/types/path.rs +++ /dev/null @@ -1,379 +0,0 @@ -//! Path extractor -use std::sync::Arc; -use std::{fmt, ops}; - -use actix_http::error::{Error, ErrorNotFound}; -use actix_router::PathDeserializer; -use futures::future::{ready, Ready}; -use serde::de; - -use crate::dev::Payload; -use crate::error::PathError; -use crate::request::HttpRequest; -use crate::FromRequest; - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's path. -/// -/// [**PathConfig**](struct.PathConfig.html) allows to configure extraction process. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// -/// /// extract path info from "/{username}/{count}/index.html" url -/// /// {username} - deserializes to a String -/// /// {count} - - deserializes to a u32 -/// async fn index(info: web::Path<(String, u32)>) -> String { -/// format!("Welcome {}! {}", info.0, info.1) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/{username}/{count}/index.html") // <- define path parameters -/// .route(web::get().to(index)) // <- register handler with `Path` extractor -/// ); -/// } -/// ``` -/// -/// It is possible to extract path information to a specific type that -/// implements `Deserialize` trait from *serde*. -/// -/// ```rust -/// use actix_web::{web, App, Error}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract `Info` from a path using serde -/// async fn index(info: web::Path) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/{username}/index.html") // <- define path parameters -/// .route(web::get().to(index)) // <- use handler with Path` extractor -/// ); -/// } -/// ``` -pub struct Path { - inner: T, -} - -impl Path { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.inner - } -} - -impl AsRef for Path { - fn as_ref(&self) -> &T { - &self.inner - } -} - -impl ops::Deref for Path { - type Target = T; - - fn deref(&self) -> &T { - &self.inner - } -} - -impl ops::DerefMut for Path { - fn deref_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl From for Path { - fn from(inner: T) -> Path { - Path { inner } - } -} - -impl fmt::Debug for Path { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.inner.fmt(f) - } -} - -impl fmt::Display for Path { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.inner.fmt(f) - } -} - -/// Extract typed information from the request's path. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// -/// /// extract path info from "/{username}/{count}/index.html" url -/// /// {username} - deserializes to a String -/// /// {count} - - deserializes to a u32 -/// async fn index(info: web::Path<(String, u32)>) -> String { -/// format!("Welcome {}! {}", info.0, info.1) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/{username}/{count}/index.html") // <- define path parameters -/// .route(web::get().to(index)) // <- register handler with `Path` extractor -/// ); -/// } -/// ``` -/// -/// It is possible to extract path information to a specific type that -/// implements `Deserialize` trait from *serde*. -/// -/// ```rust -/// use actix_web::{web, App, Error}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract `Info` from a path using serde -/// async fn index(info: web::Path) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/{username}/index.html") // <- define path parameters -/// .route(web::get().to(index)) // <- use handler with Path` extractor -/// ); -/// } -/// ``` -impl FromRequest for Path -where - T: de::DeserializeOwned, -{ - type Error = Error; - type Future = Ready>; - type Config = PathConfig; - - #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - let error_handler = req - .app_data::() - .map(|c| c.ehandler.clone()) - .unwrap_or(None); - - ready( - de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) - .map(|inner| Path { inner }) - .map_err(move |e| { - log::debug!( - "Failed during Path extractor deserialization. \ - Request path: {:?}", - req.path() - ); - if let Some(error_handler) = error_handler { - let e = PathError::Deserialize(e); - (error_handler)(e, req) - } else { - ErrorNotFound(e) - } - }), - ) - } -} - -/// Path extractor configuration -/// -/// ```rust -/// use actix_web::web::PathConfig; -/// use actix_web::{error, web, App, FromRequest, HttpResponse}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Deserialize, Debug)] -/// enum Folder { -/// #[serde(rename = "inbox")] -/// Inbox, -/// #[serde(rename = "outbox")] -/// Outbox, -/// } -/// -/// // deserialize `Info` from request's path -/// async fn index(folder: web::Path) -> String { -/// format!("Selected folder: {:?}!", folder) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/messages/{folder}") -/// .app_data(PathConfig::default().error_handler(|err, req| { -/// error::InternalError::from_response( -/// err, -/// HttpResponse::Conflict().finish(), -/// ) -/// .into() -/// })) -/// .route(web::post().to(index)), -/// ); -/// } -/// ``` -#[derive(Clone)] -pub struct PathConfig { - ehandler: Option Error + Send + Sync>>, -} - -impl PathConfig { - /// Set custom error handler - pub fn error_handler(mut self, f: F) -> Self - where - F: Fn(PathError, &HttpRequest) -> Error + Send + Sync + 'static, - { - self.ehandler = Some(Arc::new(f)); - self - } -} - -impl Default for PathConfig { - fn default() -> Self { - PathConfig { ehandler: None } - } -} - -#[cfg(test)] -mod tests { - use actix_router::ResourceDef; - use derive_more::Display; - use serde_derive::Deserialize; - - use super::*; - use crate::test::TestRequest; - use crate::{error, http, HttpResponse}; - - #[derive(Deserialize, Debug, Display)] - #[display(fmt = "MyStruct({}, {})", key, value)] - struct MyStruct { - key: String, - value: String, - } - - #[derive(Deserialize)] - struct Test2 { - key: String, - value: u32, - } - - #[actix_rt::test] - async fn test_extract_path_single() { - let resource = ResourceDef::new("/{value}/"); - - let mut req = TestRequest::with_uri("/32/").to_srv_request(); - resource.match_path(req.match_info_mut()); - - let (req, mut pl) = req.into_parts(); - assert_eq!(*Path::::from_request(&req, &mut pl).await.unwrap(), 32); - assert!(Path::::from_request(&req, &mut pl).await.is_err()); - } - - #[actix_rt::test] - async fn test_tuple_extract() { - let resource = ResourceDef::new("/{key}/{value}/"); - - let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - resource.match_path(req.match_info_mut()); - - let (req, mut pl) = req.into_parts(); - let res = <(Path<(String, String)>,)>::from_request(&req, &mut pl) - .await - .unwrap(); - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - - let res = <(Path<(String, String)>, Path<(String, String)>)>::from_request( - &req, &mut pl, - ) - .await - .unwrap(); - assert_eq!((res.0).0, "name"); - assert_eq!((res.0).1, "user1"); - assert_eq!((res.1).0, "name"); - assert_eq!((res.1).1, "user1"); - - let () = <()>::from_request(&req, &mut pl).await.unwrap(); - } - - #[actix_rt::test] - async fn test_request_extract() { - let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - - let resource = ResourceDef::new("/{key}/{value}/"); - resource.match_path(req.match_info_mut()); - - let (req, mut pl) = req.into_parts(); - let mut s = Path::::from_request(&req, &mut pl).await.unwrap(); - assert_eq!(s.key, "name"); - assert_eq!(s.value, "user1"); - s.value = "user2".to_string(); - assert_eq!(s.value, "user2"); - assert_eq!( - format!("{}, {:?}", s, s), - "MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }" - ); - let s = s.into_inner(); - assert_eq!(s.value, "user2"); - - let s = Path::<(String, String)>::from_request(&req, &mut pl) - .await - .unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, "user1"); - - let mut req = TestRequest::with_uri("/name/32/").to_srv_request(); - let resource = ResourceDef::new("/{key}/{value}/"); - resource.match_path(req.match_info_mut()); - - let (req, mut pl) = req.into_parts(); - let s = Path::::from_request(&req, &mut pl).await.unwrap(); - assert_eq!(s.as_ref().key, "name"); - assert_eq!(s.value, 32); - - let s = Path::<(String, u8)>::from_request(&req, &mut pl) - .await - .unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, 32); - - let res = Path::>::from_request(&req, &mut pl) - .await - .unwrap(); - assert_eq!(res[0], "name".to_owned()); - assert_eq!(res[1], "32".to_owned()); - } - - #[actix_rt::test] - async fn test_custom_err_handler() { - let (req, mut pl) = TestRequest::with_uri("/name/user1/") - .app_data(PathConfig::default().error_handler(|err, _| { - error::InternalError::from_response( - err, - HttpResponse::Conflict().finish(), - ) - .into() - })) - .to_http_parts(); - - let s = Path::<(usize,)>::from_request(&req, &mut pl) - .await - .unwrap_err(); - let res: HttpResponse = s.into(); - - assert_eq!(res.status(), http::StatusCode::CONFLICT); - } -} diff --git a/src/types/payload.rs b/src/types/payload.rs deleted file mode 100644 index 449e6c5b0..000000000 --- a/src/types/payload.rs +++ /dev/null @@ -1,481 +0,0 @@ -//! Payload/Bytes/String extractors -use std::future::Future; -use std::pin::Pin; -use std::str; -use std::task::{Context, Poll}; - -use actix_http::error::{Error, ErrorBadRequest, PayloadError}; -use actix_http::HttpMessage; -use bytes::{Bytes, BytesMut}; -use encoding_rs::UTF_8; -use futures::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready}; -use futures::{Stream, StreamExt}; -use mime::Mime; - -use crate::dev; -use crate::extract::FromRequest; -use crate::http::header; -use crate::request::HttpRequest; - -/// Payload extractor returns request 's payload stream. -/// -/// ## Example -/// -/// ```rust -/// use futures::{Future, Stream, StreamExt}; -/// use actix_web::{web, error, App, Error, HttpResponse}; -/// -/// /// extract binary data from request -/// async fn index(mut body: web::Payload) -> Result -/// { -/// let mut bytes = web::BytesMut::new(); -/// while let Some(item) = body.next().await { -/// bytes.extend_from_slice(&item?); -/// } -/// -/// format!("Body {:?}!", bytes); -/// Ok(HttpResponse::Ok().finish()) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::get().to(index)) -/// ); -/// } -/// ``` -pub struct Payload(pub crate::dev::Payload); - -impl Payload { - /// Deconstruct to a inner value - pub fn into_inner(self) -> crate::dev::Payload { - self.0 - } -} - -impl Stream for Payload { - type Item = Result; - - #[inline] - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(&mut self.0).poll_next(cx) - } -} - -/// Get request's payload stream -/// -/// ## Example -/// -/// ```rust -/// use futures::{Future, Stream, StreamExt}; -/// use actix_web::{web, error, App, Error, HttpResponse}; -/// -/// /// extract binary data from request -/// async fn index(mut body: web::Payload) -> Result -/// { -/// let mut bytes = web::BytesMut::new(); -/// while let Some(item) = body.next().await { -/// bytes.extend_from_slice(&item?); -/// } -/// -/// format!("Body {:?}!", bytes); -/// Ok(HttpResponse::Ok().finish()) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::get().to(index)) -/// ); -/// } -/// ``` -impl FromRequest for Payload { - type Config = PayloadConfig; - type Error = Error; - type Future = Ready>; - - #[inline] - fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { - ok(Payload(payload.take())) - } -} - -/// Request binary data from a request's payload. -/// -/// Loads request's payload and construct Bytes instance. -/// -/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure -/// extraction process. -/// -/// ## Example -/// -/// ```rust -/// use bytes::Bytes; -/// use actix_web::{web, App}; -/// -/// /// extract binary data from request -/// async fn index(body: Bytes) -> String { -/// format!("Body {:?}!", body) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::get().to(index)) -/// ); -/// } -/// ``` -impl FromRequest for Bytes { - type Config = PayloadConfig; - type Error = Error; - type Future = Either< - LocalBoxFuture<'static, Result>, - Ready>, - >; - - #[inline] - fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { - let tmp; - let cfg = if let Some(cfg) = req.app_data::() { - cfg - } else { - tmp = PayloadConfig::default(); - &tmp - }; - - if let Err(e) = cfg.check_mimetype(req) { - return Either::Right(err(e)); - } - - let limit = cfg.limit; - let fut = HttpMessageBody::new(req, payload).limit(limit); - Either::Left(async move { Ok(fut.await?) }.boxed_local()) - } -} - -/// Extract text information from a request's body. -/// -/// Text extractor automatically decode body according to the request's charset. -/// -/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure -/// extraction process. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App, FromRequest}; -/// -/// /// extract text data from request -/// async fn index(text: String) -> String { -/// format!("Body {}!", text) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html") -/// .app_data(String::configure(|cfg| { // <- limit size of the payload -/// cfg.limit(4096) -/// })) -/// .route(web::get().to(index)) // <- register handler with extractor params -/// ); -/// } -/// ``` -impl FromRequest for String { - type Config = PayloadConfig; - type Error = Error; - type Future = Either< - LocalBoxFuture<'static, Result>, - Ready>, - >; - - #[inline] - fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { - let tmp; - let cfg = if let Some(cfg) = req.app_data::() { - cfg - } else { - tmp = PayloadConfig::default(); - &tmp - }; - - // check content-type - if let Err(e) = cfg.check_mimetype(req) { - return Either::Right(err(e)); - } - - // check charset - let encoding = match req.encoding() { - Ok(enc) => enc, - Err(e) => return Either::Right(err(e.into())), - }; - let limit = cfg.limit; - let fut = HttpMessageBody::new(req, payload).limit(limit); - - Either::Left( - async move { - let body = fut.await?; - - if encoding == UTF_8 { - Ok(str::from_utf8(body.as_ref()) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - .to_owned()) - } else { - Ok(encoding - .decode_without_bom_handling_and_without_replacement(&body) - .map(|s| s.into_owned()) - .ok_or_else(|| ErrorBadRequest("Can not decode body"))?) - } - } - .boxed_local(), - ) - } -} -/// Payload configuration for request's payload. -#[derive(Clone)] -pub struct PayloadConfig { - limit: usize, - mimetype: Option, -} - -impl PayloadConfig { - /// Create `PayloadConfig` instance and set max size of payload. - pub fn new(limit: usize) -> Self { - let mut cfg = Self::default(); - cfg.limit = limit; - cfg - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set required mime-type of the request. By default mime type is not - /// enforced. - pub fn mimetype(mut self, mt: Mime) -> Self { - self.mimetype = Some(mt); - self - } - - fn check_mimetype(&self, req: &HttpRequest) -> Result<(), Error> { - // check content-type - if let Some(ref mt) = self.mimetype { - match req.mime_type() { - Ok(Some(ref req_mt)) => { - if mt != req_mt { - return Err(ErrorBadRequest("Unexpected Content-Type")); - } - } - Ok(None) => { - return Err(ErrorBadRequest("Content-Type is expected")); - } - Err(err) => { - return Err(err.into()); - } - } - } - Ok(()) - } -} - -impl Default for PayloadConfig { - fn default() -> Self { - PayloadConfig { - limit: 262_144, - mimetype: None, - } - } -} - -/// Future that resolves to a complete http message body. -/// -/// Load http message body. -/// -/// By default only 256Kb payload reads to a memory, then -/// `PayloadError::Overflow` get returned. Use `MessageBody::limit()` -/// method to change upper limit. -pub struct HttpMessageBody { - limit: usize, - length: Option, - #[cfg(feature = "compress")] - stream: Option>, - #[cfg(not(feature = "compress"))] - stream: Option, - err: Option, - fut: Option>>, -} - -impl HttpMessageBody { - /// Create `MessageBody` for request. - pub fn new(req: &HttpRequest, payload: &mut dev::Payload) -> HttpMessageBody { - let mut len = None; - if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } else { - return Self::err(PayloadError::UnknownLength); - } - } else { - return Self::err(PayloadError::UnknownLength); - } - } - - #[cfg(feature = "compress")] - let stream = Some(dev::Decompress::from_headers(payload.take(), req.headers())); - #[cfg(not(feature = "compress"))] - let stream = Some(payload.take()); - - HttpMessageBody { - stream, - limit: 262_144, - length: len, - fut: None, - err: None, - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - fn err(e: PayloadError) -> Self { - HttpMessageBody { - stream: None, - limit: 262_144, - fut: None, - err: Some(e), - length: None, - } - } -} - -impl Future for HttpMessageBody { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if let Some(ref mut fut) = self.fut { - return Pin::new(fut).poll(cx); - } - - if let Some(err) = self.err.take() { - return Poll::Ready(Err(err)); - } - - if let Some(len) = self.length.take() { - if len > self.limit { - return Poll::Ready(Err(PayloadError::Overflow)); - } - } - - // future - let limit = self.limit; - let mut stream = self.stream.take().unwrap(); - self.fut = Some( - async move { - let mut body = BytesMut::with_capacity(8192); - - while let Some(item) = stream.next().await { - let chunk = item?; - if body.len() + chunk.len() > limit { - return Err(PayloadError::Overflow); - } else { - body.extend_from_slice(&chunk); - } - } - Ok(body.freeze()) - } - .boxed_local(), - ); - self.poll(cx) - } -} - -#[cfg(test)] -mod tests { - use bytes::Bytes; - - use super::*; - use crate::http::header; - use crate::test::TestRequest; - - #[actix_rt::test] - async fn test_payload_config() { - let req = TestRequest::default().to_http_request(); - let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON); - assert!(cfg.check_mimetype(&req).is_err()); - - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .to_http_request(); - assert!(cfg.check_mimetype(&req).is_err()); - - let req = TestRequest::with_header(header::CONTENT_TYPE, "application/json") - .to_http_request(); - assert!(cfg.check_mimetype(&req).is_ok()); - } - - #[actix_rt::test] - async fn test_bytes() { - let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .to_http_parts(); - - let s = Bytes::from_request(&req, &mut pl).await.unwrap(); - assert_eq!(s, Bytes::from_static(b"hello=world")); - } - - #[actix_rt::test] - async fn test_string() { - let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .to_http_parts(); - - let s = String::from_request(&req, &mut pl).await.unwrap(); - assert_eq!(s, "hello=world"); - } - - #[actix_rt::test] - async fn test_message_body() { - let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx") - .to_srv_request() - .into_parts(); - let res = HttpMessageBody::new(&req, &mut pl).await; - match res.err().unwrap() { - PayloadError::UnknownLength => (), - _ => unreachable!("error"), - } - - let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "1000000") - .to_srv_request() - .into_parts(); - let res = HttpMessageBody::new(&req, &mut pl).await; - match res.err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } - - let (req, mut pl) = TestRequest::default() - .set_payload(Bytes::from_static(b"test")) - .to_http_parts(); - let res = HttpMessageBody::new(&req, &mut pl).await; - assert_eq!(res.ok().unwrap(), Bytes::from_static(b"test")); - - let (req, mut pl) = TestRequest::default() - .set_payload(Bytes::from_static(b"11111111111111")) - .to_http_parts(); - let res = HttpMessageBody::new(&req, &mut pl).limit(5).await; - match res.err().unwrap() { - PayloadError::Overflow => (), - _ => unreachable!("error"), - } - } -} diff --git a/src/types/query.rs b/src/types/query.rs deleted file mode 100644 index a6c18d9be..000000000 --- a/src/types/query.rs +++ /dev/null @@ -1,297 +0,0 @@ -//! Query extractor - -use std::sync::Arc; -use std::{fmt, ops}; - -use actix_http::error::Error; -use futures::future::{err, ok, Ready}; -use serde::de; -use serde_urlencoded; - -use crate::dev::Payload; -use crate::error::QueryPayloadError; -use crate::extract::FromRequest; -use crate::request::HttpRequest; - -/// Extract typed information from the request's query. -/// -/// **Note**: A query string consists of unordered `key=value` pairs, therefore it cannot -/// be decoded into any type which depends upon data ordering e.g. tuples or tuple-structs. -/// Attempts to do so will *fail at runtime*. -/// -/// [**QueryConfig**](struct.QueryConfig.html) allows to configure extraction process. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Debug, Deserialize)] -/// pub enum ResponseType { -/// Token, -/// Code -/// } -/// -/// #[derive(Deserialize)] -/// pub struct AuthRequest { -/// id: u64, -/// response_type: ResponseType, -/// } -/// -/// // Use `Query` extractor for query information (and destructure it within the signature). -/// // This handler gets called only if the request's query string contains a `username` field. -/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`. -/// async fn index(web::Query(info): web::Query) -> String { -/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor -/// } -/// ``` -#[derive(PartialEq, Eq, PartialOrd, Ord)] -pub struct Query(pub T); - -impl Query { - /// Deconstruct to a inner value - pub fn into_inner(self) -> T { - self.0 - } - - /// Get query parameters from the path - pub fn from_query(query_str: &str) -> Result - where - T: de::DeserializeOwned, - { - serde_urlencoded::from_str::(query_str) - .map(|val| Ok(Query(val))) - .unwrap_or_else(move |e| Err(QueryPayloadError::Deserialize(e))) - } -} - -impl ops::Deref for Query { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl ops::DerefMut for Query { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl fmt::Debug for Query { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for Query { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -/// Extract typed information from the request's query. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Debug, Deserialize)] -/// pub enum ResponseType { -/// Token, -/// Code -/// } -/// -/// #[derive(Deserialize)] -/// pub struct AuthRequest { -/// id: u64, -/// response_type: ResponseType, -/// } -/// -/// // Use `Query` extractor for query information. -/// // This handler get called only if request's query contains `username` field -/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// async fn index(info: web::Query) -> String { -/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html") -/// .route(web::get().to(index))); // <- use `Query` extractor -/// } -/// ``` -impl FromRequest for Query -where - T: de::DeserializeOwned, -{ - type Error = Error; - type Future = Ready>; - type Config = QueryConfig; - - #[inline] - fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - let error_handler = req - .app_data::() - .map(|c| c.ehandler.clone()) - .unwrap_or(None); - - serde_urlencoded::from_str::(req.query_string()) - .map(|val| ok(Query(val))) - .unwrap_or_else(move |e| { - let e = QueryPayloadError::Deserialize(e); - - log::debug!( - "Failed during Query extractor deserialization. \ - Request path: {:?}", - req.path() - ); - - let e = if let Some(error_handler) = error_handler { - (error_handler)(e, req) - } else { - e.into() - }; - - err(e) - }) - } -} - -/// Query extractor configuration -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{error, web, App, FromRequest, HttpResponse}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's querystring -/// async fn index(info: web::Query) -> String { -/// format!("Welcome {}!", info.username) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").app_data( -/// // change query extractor configuration -/// web::Query::::configure(|cfg| { -/// cfg.error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// }) -/// })) -/// .route(web::post().to(index)) -/// ); -/// } -/// ``` -#[derive(Clone)] -pub struct QueryConfig { - ehandler: - Option Error + Send + Sync>>, -} - -impl QueryConfig { - /// Set custom error handler - pub fn error_handler(mut self, f: F) -> Self - where - F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, - { - self.ehandler = Some(Arc::new(f)); - self - } -} - -impl Default for QueryConfig { - fn default() -> Self { - QueryConfig { ehandler: None } - } -} - -#[cfg(test)] -mod tests { - use actix_http::http::StatusCode; - use derive_more::Display; - use serde_derive::Deserialize; - - use super::*; - use crate::error::InternalError; - use crate::test::TestRequest; - use crate::HttpResponse; - - #[derive(Deserialize, Debug, Display)] - struct Id { - id: String, - } - - #[actix_rt::test] - async fn test_service_request_extract() { - let req = TestRequest::with_uri("/name/user1/").to_srv_request(); - assert!(Query::::from_query(&req.query_string()).is_err()); - - let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - let mut s = Query::::from_query(&req.query_string()).unwrap(); - - assert_eq!(s.id, "test"); - assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); - - s.id = "test1".to_string(); - let s = s.into_inner(); - assert_eq!(s.id, "test1"); - } - - #[actix_rt::test] - async fn test_request_extract() { - let req = TestRequest::with_uri("/name/user1/").to_srv_request(); - let (req, mut pl) = req.into_parts(); - assert!(Query::::from_request(&req, &mut pl).await.is_err()); - - let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request(); - let (req, mut pl) = req.into_parts(); - - let mut s = Query::::from_request(&req, &mut pl).await.unwrap(); - assert_eq!(s.id, "test"); - assert_eq!(format!("{}, {:?}", s, s), "test, Id { id: \"test\" }"); - - s.id = "test1".to_string(); - let s = s.into_inner(); - assert_eq!(s.id, "test1"); - } - - #[actix_rt::test] - async fn test_custom_error_responder() { - let req = TestRequest::with_uri("/name/user1/") - .app_data(QueryConfig::default().error_handler(|e, _| { - let resp = HttpResponse::UnprocessableEntity().finish(); - InternalError::from_response(e, resp).into() - })) - .to_srv_request(); - - let (req, mut pl) = req.into_parts(); - let query = Query::::from_request(&req, &mut pl).await; - - assert!(query.is_err()); - assert_eq!( - query - .unwrap_err() - .as_response_error() - .error_response() - .status(), - StatusCode::UNPROCESSABLE_ENTITY - ); - } -} diff --git a/src/types/readlines.rs b/src/types/readlines.rs deleted file mode 100644 index 82853381b..000000000 --- a/src/types/readlines.rs +++ /dev/null @@ -1,206 +0,0 @@ -use std::borrow::Cow; -use std::pin::Pin; -use std::str; -use std::task::{Context, Poll}; - -use bytes::{Bytes, BytesMut}; -use encoding_rs::{Encoding, UTF_8}; -use futures::Stream; - -use crate::dev::Payload; -use crate::error::{PayloadError, ReadlinesError}; -use crate::HttpMessage; - -/// Stream to read request line by line. -pub struct Readlines { - stream: Payload, - buff: BytesMut, - limit: usize, - checked_buff: bool, - encoding: &'static Encoding, - err: Option, -} - -impl Readlines -where - T: HttpMessage, - T::Stream: Stream> + Unpin, -{ - /// Create a new stream to read request line by line. - pub fn new(req: &mut T) -> Self { - let encoding = match req.encoding() { - Ok(enc) => enc, - Err(err) => return Self::err(err.into()), - }; - - Readlines { - stream: req.take_payload(), - buff: BytesMut::with_capacity(262_144), - limit: 262_144, - checked_buff: true, - err: None, - encoding, - } - } - - /// Change max line size. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - fn err(err: ReadlinesError) -> Self { - Readlines { - stream: Payload::None, - buff: BytesMut::new(), - limit: 262_144, - checked_buff: true, - encoding: UTF_8, - err: Some(err), - } - } -} - -impl Stream for Readlines -where - T: HttpMessage, - T::Stream: Stream> + Unpin, -{ - type Item = Result; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - let this = self.get_mut(); - - if let Some(err) = this.err.take() { - return Poll::Ready(Some(Err(err))); - } - - // check if there is a newline in the buffer - if !this.checked_buff { - let mut found: Option = None; - for (ind, b) in this.buff.iter().enumerate() { - if *b == b'\n' { - found = Some(ind); - break; - } - } - if let Some(ind) = found { - // check if line is longer than limit - if ind + 1 > this.limit { - return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow))); - } - let line = if this.encoding == UTF_8 { - str::from_utf8(&this.buff.split_to(ind + 1)) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - this.encoding - .decode_without_bom_handling_and_without_replacement( - &this.buff.split_to(ind + 1), - ) - .map(Cow::into_owned) - .ok_or(ReadlinesError::EncodingError)? - }; - return Poll::Ready(Some(Ok(line))); - } - this.checked_buff = true; - } - // poll req for more bytes - match Pin::new(&mut this.stream).poll_next(cx) { - Poll::Ready(Some(Ok(mut bytes))) => { - // check if there is a newline in bytes - let mut found: Option = None; - for (ind, b) in bytes.iter().enumerate() { - if *b == b'\n' { - found = Some(ind); - break; - } - } - if let Some(ind) = found { - // check if line is longer than limit - if ind + 1 > this.limit { - return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow))); - } - let line = if this.encoding == UTF_8 { - str::from_utf8(&bytes.split_to(ind + 1)) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - this.encoding - .decode_without_bom_handling_and_without_replacement( - &bytes.split_to(ind + 1), - ) - .map(Cow::into_owned) - .ok_or(ReadlinesError::EncodingError)? - }; - // extend buffer with rest of the bytes; - this.buff.extend_from_slice(&bytes); - this.checked_buff = false; - return Poll::Ready(Some(Ok(line))); - } - this.buff.extend_from_slice(&bytes); - Poll::Pending - } - Poll::Pending => Poll::Pending, - Poll::Ready(None) => { - if this.buff.is_empty() { - return Poll::Ready(None); - } - if this.buff.len() > this.limit { - return Poll::Ready(Some(Err(ReadlinesError::LimitOverflow))); - } - let line = if this.encoding == UTF_8 { - str::from_utf8(&this.buff) - .map_err(|_| ReadlinesError::EncodingError)? - .to_owned() - } else { - this.encoding - .decode_without_bom_handling_and_without_replacement(&this.buff) - .map(Cow::into_owned) - .ok_or(ReadlinesError::EncodingError)? - }; - this.buff.clear(); - Poll::Ready(Some(Ok(line))) - } - Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ReadlinesError::from(e)))), - } - } -} - -#[cfg(test)] -mod tests { - use futures::stream::StreamExt; - - use super::*; - use crate::test::TestRequest; - - #[actix_rt::test] - async fn test_readlines() { - let mut req = TestRequest::default() - .set_payload(Bytes::from_static( - b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ - industry. Lorem Ipsum has been the industry's standard dummy\n\ - Contrary to popular belief, Lorem Ipsum is not simply random text.", - )) - .to_request(); - - let mut stream = Readlines::new(&mut req); - assert_eq!( - stream.next().await.unwrap().unwrap(), - "Lorem Ipsum is simply dummy text of the printing and typesetting\n" - ); - - assert_eq!( - stream.next().await.unwrap().unwrap(), - "industry. Lorem Ipsum has been the industry's standard dummy\n" - ); - - assert_eq!( - stream.next().await.unwrap().unwrap(), - "Contrary to popular belief, Lorem Ipsum is not simply random text." - ); - } -} diff --git a/src/web.rs b/src/web.rs deleted file mode 100644 index 50d99479a..000000000 --- a/src/web.rs +++ /dev/null @@ -1,266 +0,0 @@ -//! Essentials helper functions and types for application registration. -use actix_http::http::Method; -use actix_router::IntoPattern; -use futures::Future; - -pub use actix_http::Response as HttpResponse; -pub use bytes::{Bytes, BytesMut}; -pub use futures::channel::oneshot::Canceled; - -use crate::error::BlockingError; -use crate::extract::FromRequest; -use crate::handler::Factory; -use crate::resource::Resource; -use crate::responder::Responder; -use crate::route::Route; -use crate::scope::Scope; -use crate::service::WebService; - -pub use crate::config::ServiceConfig; -pub use crate::data::Data; -pub use crate::request::HttpRequest; -pub use crate::types::*; - -/// Create resource for a specific path. -/// -/// Resources may have variable path segments. For example, a -/// resource with the path `/a/{name}/c` would match all incoming -/// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. -/// -/// 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. This is done by -/// looking up the identifier in the `Params` object returned by -/// `HttpRequest.match_info()` method. -/// -/// By default, each segment matches the regular expression `[^{}/]+`. -/// -/// You can also specify a custom regex in the form `{identifier:regex}`: -/// -/// For instance, to route `GET`-requests on any route matching -/// `/users/{userid}/{friend}` and store `userid` and `friend` in -/// the exposed `Params` object: -/// -/// ```rust -/// # extern crate actix_web; -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/users/{userid}/{friend}") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// ``` -pub fn resource(path: T) -> Resource { - Resource::new(path) -} - -/// Configure scope for common root path. -/// -/// Scopes collect multiple paths under a common path prefix. -/// Scope path can contain variable path segments as resources. -/// -/// ```rust -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::scope("/{project_id}") -/// .service(web::resource("/path1").to(|| HttpResponse::Ok())) -/// .service(web::resource("/path2").to(|| HttpResponse::Ok())) -/// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// ``` -/// -/// In the above example, three routes get added: -/// * /{project_id}/path1 -/// * /{project_id}/path2 -/// * /{project_id}/path3 -/// -pub fn scope(path: &str) -> Scope { - Scope::new(path) -} - -/// Create *route* without configuration. -pub fn route() -> Route { - Route::new() -} - -/// Create *route* with `GET` method guard. -/// -/// ```rust -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `GET` route get added: -/// * /{project_id} -/// -pub fn get() -> Route { - method(Method::GET) -} - -/// Create *route* with `POST` method guard. -/// -/// ```rust -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::post().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `POST` route get added: -/// * /{project_id} -/// -pub fn post() -> Route { - method(Method::POST) -} - -/// Create *route* with `PUT` method guard. -/// -/// ```rust -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::put().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `PUT` route get added: -/// * /{project_id} -/// -pub fn put() -> Route { - method(Method::PUT) -} - -/// Create *route* with `PATCH` method guard. -/// -/// ```rust -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::patch().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `PATCH` route get added: -/// * /{project_id} -/// -pub fn patch() -> Route { - method(Method::PATCH) -} - -/// Create *route* with `DELETE` method guard. -/// -/// ```rust -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::delete().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `DELETE` route get added: -/// * /{project_id} -/// -pub fn delete() -> Route { - method(Method::DELETE) -} - -/// Create *route* with `HEAD` method guard. -/// -/// ```rust -/// use actix_web::{web, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::head().to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `HEAD` route get added: -/// * /{project_id} -/// -pub fn head() -> Route { - method(Method::HEAD) -} - -/// Create *route* and add method guard. -/// -/// ```rust -/// use actix_web::{web, http, App, HttpResponse}; -/// -/// let app = App::new().service( -/// web::resource("/{project_id}") -/// .route(web::method(http::Method::GET).to(|| HttpResponse::Ok())) -/// ); -/// ``` -/// -/// In the above example, one `GET` route get added: -/// * /{project_id} -/// -pub fn method(method: Method) -> Route { - Route::new().method(method) -} - -/// Create a new route and add handler. -/// -/// ```rust -/// use actix_web::{web, App, HttpResponse, Responder}; -/// -/// async fn index() -> impl Responder { -/// HttpResponse::Ok() -/// } -/// -/// App::new().service( -/// web::resource("/").route( -/// web::to(index)) -/// ); -/// ``` -pub fn to(handler: F) -> Route -where - F: Factory, - I: FromRequest + 'static, - R: Future + 'static, - U: Responder + 'static, -{ - Route::new().to(handler) -} - -/// Create raw service for a specific path. -/// -/// ```rust -/// use actix_web::{dev, web, guard, App, Error, HttpResponse}; -/// -/// async fn my_service(req: dev::ServiceRequest) -> Result { -/// Ok(req.into_response(HttpResponse::Ok().finish())) -/// } -/// -/// let app = App::new().service( -/// web::service("/users/*") -/// .guard(guard::Header("content-type", "text/plain")) -/// .finish(my_service) -/// ); -/// ``` -pub fn service(path: T) -> WebService { - WebService::new(path) -} - -/// Execute blocking function on a thread pool, returns future that resolves -/// to result of the function execution. -pub async fn block(f: F) -> Result> -where - F: FnOnce() -> Result + Send + 'static, - I: Send + 'static, - E: Send + std::fmt::Debug + 'static, -{ - actix_threadpool::run(f).await -} diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md deleted file mode 100644 index 617b8092f..000000000 --- a/test-server/CHANGES.md +++ /dev/null @@ -1,78 +0,0 @@ -# Changes - -## [Unreleased] - 2020-xx-xx - -* Update the `time` dependency to 0.2.5 - - -## [1.0.0] - 2019-12-13 - -### Changed - -* Replaced `TestServer::start()` with `test_server()` - - -## [1.0.0-alpha.3] - 2019-12-07 - -### Changed - -* Migrate to `std::future` - - -## [0.2.5] - 2019-09-17 - -### Changed - -* Update serde_urlencoded to "0.6.1" -* Increase TestServerRuntime timeouts from 500ms to 3000ms - -### Fixed - -* Do not override current `System` - - -## [0.2.4] - 2019-07-18 - -* Update actix-server to 0.6 - -## [0.2.3] - 2019-07-16 - -* Add `delete`, `options`, `patch` methods to `TestServerRunner` - -## [0.2.2] - 2019-06-16 - -* Add .put() and .sput() methods - -## [0.2.1] - 2019-06-05 - -* Add license files - -## [0.2.0] - 2019-05-12 - -* Update awc and actix-http deps - -## [0.1.1] - 2019-04-24 - -* Always make new connection for http client - - -## [0.1.0] - 2019-04-16 - -* No changes - - -## [0.1.0-alpha.3] - 2019-04-02 - -* Request functions accept path #743 - - -## [0.1.0-alpha.2] - 2019-03-29 - -* Added TestServerRuntime::load_body() method - -* Update actix-http and awc libraries - - -## [0.1.0-alpha.1] - 2019-03-28 - -* Initial impl diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml deleted file mode 100644 index b22414e29..000000000 --- a/test-server/Cargo.toml +++ /dev/null @@ -1,59 +0,0 @@ -[package] -name = "actix-http-test" -version = "1.0.0" -authors = ["Nikolay Kim "] -description = "Actix http test 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-http-test/" -categories = ["network-programming", "asynchronous", - "web-programming::http-server", - "web-programming::websocket"] -license = "MIT/Apache-2.0" -exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] -edition = "2018" -workspace = ".." - -[package.metadata.docs.rs] -features = [] - -[lib] -name = "actix_http_test" -path = "src/lib.rs" - -[features] -default = [] - -# openssl -openssl = ["open-ssl", "awc/openssl"] - -[dependencies] -actix-service = "1.0.1" -actix-codec = "0.2.0" -actix-connect = "1.0.0" -actix-utils = "1.0.3" -actix-rt = "1.0.0" -actix-server = "1.0.0" -actix-testing = "1.0.0" -awc = "1.0.0" - -base64 = "0.11" -bytes = "0.5.3" -futures = "0.3.1" -http = "0.2.0" -log = "0.4" -env_logger = "0.6" -net2 = "0.2" -serde = "1.0" -serde_json = "1.0" -sha1 = "0.6" -slab = "0.4" -serde_urlencoded = "0.6.1" -time = { version = "0.2.5", default-features = false, features = ["std"] } -open-ssl = { version="0.10", package="openssl", optional = true } - -[dev-dependencies] -actix-web = "2.0.0-rc" -actix-http = "1.0.1" diff --git a/test-server/LICENSE-APACHE b/test-server/LICENSE-APACHE deleted file mode 120000 index 965b606f3..000000000 --- a/test-server/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/test-server/LICENSE-MIT b/test-server/LICENSE-MIT deleted file mode 120000 index 76219eb72..000000000 --- a/test-server/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/test-server/README.md b/test-server/README.md deleted file mode 100644 index e40650124..000000000 --- a/test-server/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Actix http test server [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-http-test)](https://crates.io/crates/actix-http-test) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -## Documentation & community resources - -* [User Guide](https://actix.rs/docs/) -* [API Documentation](https://docs.rs/actix-http-test/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-http-test](https://crates.io/crates/actix-http-test) -* Minimum supported Rust version: 1.33 or later diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs deleted file mode 100644 index 27326c67a..000000000 --- a/test-server/src/lib.rs +++ /dev/null @@ -1,259 +0,0 @@ -//! Various helpers for Actix applications to use during testing. -use std::sync::mpsc; -use std::{net, thread, time}; - -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_rt::{net::TcpStream, System}; -use actix_server::{Server, ServiceFactory}; -use awc::{error::PayloadError, ws, Client, ClientRequest, ClientResponse, Connector}; -use bytes::Bytes; -use futures::Stream; -use http::Method; -use net2::TcpBuilder; - -pub use actix_testing::*; - -/// Start test server -/// -/// `TestServer` is very simple test server that simplify process of writing -/// integration tests cases for actix web applications. -/// -/// # Examples -/// -/// ```rust -/// use actix_http::HttpService; -/// use actix_http_test::TestServer; -/// use actix_web::{web, App, HttpResponse, Error}; -/// -/// async fn my_handler() -> Result { -/// Ok(HttpResponse::Ok().into()) -/// } -/// -/// #[actix_rt::test] -/// async fn test_example() { -/// let mut srv = TestServer::start( -/// || HttpService::new( -/// App::new().service( -/// web::resource("/").to(my_handler)) -/// ) -/// ); -/// -/// let req = srv.get("/"); -/// let response = req.send().await.unwrap(); -/// assert!(response.status().is_success()); -/// } -/// ``` -pub fn test_server>(factory: F) -> TestServer { - let (tx, rx) = mpsc::channel(); - - // run server in separate thread - thread::spawn(move || { - let sys = System::new("actix-test-server"); - let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); - let local_addr = tcp.local_addr().unwrap(); - - Server::build() - .listen("test", tcp, factory)? - .workers(1) - .disable_signals() - .start(); - - tx.send((System::current(), local_addr)).unwrap(); - sys.run() - }); - - let (system, addr) = rx.recv().unwrap(); - - let client = { - let connector = { - #[cfg(feature = "openssl")] - { - use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(30000)) - .ssl(builder.build()) - .finish() - } - #[cfg(not(feature = "openssl"))] - { - Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(30000)) - .finish() - } - }; - - Client::build().connector(connector).finish() - }; - actix_connect::start_default_resolver(); - - TestServer { - addr, - client, - system, - } -} - -/// Get first available unused address -pub fn unused_addr() -> net::SocketAddr { - let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = TcpBuilder::new_v4().unwrap(); - socket.bind(&addr).unwrap(); - socket.reuse_address(true).unwrap(); - let tcp = socket.to_tcp_listener().unwrap(); - tcp.local_addr().unwrap() -} - -/// Test server controller -pub struct TestServer { - addr: net::SocketAddr, - client: Client, - system: System, -} - -impl TestServer { - /// Construct test server url - pub fn addr(&self) -> net::SocketAddr { - self.addr - } - - /// Construct test server url - pub fn url(&self, uri: &str) -> String { - if uri.starts_with('/') { - format!("http://localhost:{}{}", self.addr.port(), uri) - } else { - format!("http://localhost:{}/{}", self.addr.port(), uri) - } - } - - /// Construct test https server url - pub fn surl(&self, uri: &str) -> String { - if uri.starts_with('/') { - format!("https://localhost:{}{}", self.addr.port(), uri) - } else { - format!("https://localhost:{}/{}", self.addr.port(), uri) - } - } - - /// Create `GET` request - pub fn get>(&self, path: S) -> ClientRequest { - self.client.get(self.url(path.as_ref()).as_str()) - } - - /// Create https `GET` request - pub fn sget>(&self, path: S) -> ClientRequest { - self.client.get(self.surl(path.as_ref()).as_str()) - } - - /// Create `POST` request - pub fn post>(&self, path: S) -> ClientRequest { - self.client.post(self.url(path.as_ref()).as_str()) - } - - /// Create https `POST` request - pub fn spost>(&self, path: S) -> ClientRequest { - self.client.post(self.surl(path.as_ref()).as_str()) - } - - /// Create `HEAD` request - pub fn head>(&self, path: S) -> ClientRequest { - self.client.head(self.url(path.as_ref()).as_str()) - } - - /// Create https `HEAD` request - pub fn shead>(&self, path: S) -> ClientRequest { - self.client.head(self.surl(path.as_ref()).as_str()) - } - - /// Create `PUT` request - pub fn put>(&self, path: S) -> ClientRequest { - self.client.put(self.url(path.as_ref()).as_str()) - } - - /// Create https `PUT` request - pub fn sput>(&self, path: S) -> ClientRequest { - self.client.put(self.surl(path.as_ref()).as_str()) - } - - /// Create `PATCH` request - pub fn patch>(&self, path: S) -> ClientRequest { - self.client.patch(self.url(path.as_ref()).as_str()) - } - - /// Create https `PATCH` request - pub fn spatch>(&self, path: S) -> ClientRequest { - self.client.patch(self.surl(path.as_ref()).as_str()) - } - - /// Create `DELETE` request - pub fn delete>(&self, path: S) -> ClientRequest { - self.client.delete(self.url(path.as_ref()).as_str()) - } - - /// Create https `DELETE` request - pub fn sdelete>(&self, path: S) -> ClientRequest { - self.client.delete(self.surl(path.as_ref()).as_str()) - } - - /// Create `OPTIONS` request - pub fn options>(&self, path: S) -> ClientRequest { - self.client.options(self.url(path.as_ref()).as_str()) - } - - /// Create https `OPTIONS` request - pub fn soptions>(&self, path: S) -> ClientRequest { - self.client.options(self.surl(path.as_ref()).as_str()) - } - - /// Connect to test http server - pub fn request>(&self, method: Method, path: S) -> ClientRequest { - self.client.request(method, path.as_ref()) - } - - pub async fn load_body( - &mut self, - mut response: ClientResponse, - ) -> Result - where - S: Stream> + Unpin + 'static, - { - response.body().limit(10_485_760).await - } - - /// Connect to websocket server at a given path - pub async fn ws_at( - &mut self, - path: &str, - ) -> Result, awc::error::WsClientError> - { - let url = self.url(path); - let connect = self.client.ws(url).connect(); - connect.await.map(|(_, framed)| framed) - } - - /// Connect to a websocket server - pub async fn ws( - &mut self, - ) -> Result, awc::error::WsClientError> - { - self.ws_at("/").await - } - - /// Stop http server - fn stop(&mut self) { - self.system.stop(); - } -} - -impl Drop for TestServer { - fn drop(&mut self) { - self.stop() - } -} diff --git a/tests/cert.pem b/tests/cert.pem deleted file mode 100644 index 0eeb6721d..000000000 --- a/tests/cert.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDEDCCAfgCCQCQdmIZc/Ib/jANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQGEwJ1 -czELMAkGA1UECAwCY2ExCzAJBgNVBAcMAnNmMSEwHwYJKoZIhvcNAQkBFhJmYWZo -cmQ5MUBnbWFpbC5jb20wHhcNMTkxMTE5MTEwNjU1WhcNMjkxMTE2MTEwNjU1WjBK -MQswCQYDVQQGEwJ1czELMAkGA1UECAwCY2ExCzAJBgNVBAcMAnNmMSEwHwYJKoZI -hvcNAQkBFhJmYWZocmQ5MUBnbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQDcnaz12CKzUL7248V7Axhms/O9UQXfAdw0yolEfC3P5jADa/1C -+kLWKjAc2coqDSbGsrsR6KiH2g06Kunx+tSGqUO+Sct7HEehmxndiSwx/hfMWezy -XRe/olcHFTeCk/Tllz4xGEplhPua6GLhJygLOhAMiV8cwCYrgyPqsDduExLDFCqc -K2xntIPreumXpiE3QY4+MWyteiJko4IWDFf/UwwsdCY5MlFfw1F/Uv9vz7FfOfvu -GccHd/ex8cOwotUqd6emZb+0bVE24Sv8U+yLnHIVx/tOkxgMAnJEpAnf2G3Wp3zU -b2GJosbmfGaf+xTfnGGhTLLL7kCtva+NvZr5AgMBAAEwDQYJKoZIhvcNAQELBQAD -ggEBANftoL8zDGrjCwWvct8kOOqset2ukK8vjIGwfm88CKsy0IfSochNz2qeIu9R -ZuO7c0pfjmRkir9ZQdq9vXgG3ccL9UstFsferPH9W3YJ83kgXg3fa0EmCiN/0hwz -6Ij1ZBiN1j3+d6+PJPgyYFNu2nGwox5mJ9+aRAGe0/9c63PEOY8P2TI4HsiPmYSl -fFR8k/03vr6e+rTKW85BgctjvYKe/TnFxeCQ7dZ+na7vlEtch4tNmy6O/vEk2kCt -5jW0DUxhmRsv2wGmfFRI0+LotHjoXQQZi6nN5aGL3odaGF3gYwIVlZNd3AdkwDQz -BzG0ZwXuDDV9bSs3MfWEWcy4xuU= ------END CERTIFICATE----- diff --git a/tests/key.pem b/tests/key.pem deleted file mode 100644 index a6d308168..000000000 --- a/tests/key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDcnaz12CKzUL72 -48V7Axhms/O9UQXfAdw0yolEfC3P5jADa/1C+kLWKjAc2coqDSbGsrsR6KiH2g06 -Kunx+tSGqUO+Sct7HEehmxndiSwx/hfMWezyXRe/olcHFTeCk/Tllz4xGEplhPua -6GLhJygLOhAMiV8cwCYrgyPqsDduExLDFCqcK2xntIPreumXpiE3QY4+MWyteiJk -o4IWDFf/UwwsdCY5MlFfw1F/Uv9vz7FfOfvuGccHd/ex8cOwotUqd6emZb+0bVE2 -4Sv8U+yLnHIVx/tOkxgMAnJEpAnf2G3Wp3zUb2GJosbmfGaf+xTfnGGhTLLL7kCt -va+NvZr5AgMBAAECggEBAKoU0UwzVgVCQgca8Jt2dnBvWYDhnxIfYAI/BvaKedMm -1ms87OKfB7oOiksjyI0E2JklH72dzZf2jm4CuZt5UjGC+xwPzlTaJ4s6hQVbBHyC -NRyxU1BCXtW5tThbrhD4OjxqjmLRJEIB9OunLtwAEQoeuFLB8Va7+HFhR+Zd9k3f -7aVA93pC5A50NRbZlke4miJ3Q8n7ZF0+UmxkBfm3fbqLk7aMWkoEKwLLTadjRlu1 -bBp0YDStX66I/p1kujqBOdh6VpPvxFOa1sV9pq0jeiGc9YfSkzRSKzIn8GoyviFB -fHeszQdNlcnrSDSNnMABAw+ZpxUO7SCaftjwejEmKZUCgYEA+TY43VpmV95eY7eo -WKwGepiHE0fwQLuKGELmZdZI80tFi73oZMuiB5WzwmkaKGcJmm7KGE9KEvHQCo9j -xvmktBR0VEZH8pmVfun+4h6+0H7m/NKMBBeOyv/IK8jBgHjkkB6e6nmeR7CqTxCw -tf9tbajl1QN8gNzXZSjBDT/lanMCgYEA4qANOKOSiEARtgwyXQeeSJcM2uPv6zF3 -ffM7vjSedtuEOHUSVeyBP/W8KDt7zyPppO/WNbURHS+HV0maS9yyj6zpVS2HGmbs -3fetswsQ+zYVdokW89x4oc2z4XOGHd1LcSlyhRwPt0u2g1E9L0irwTQLWU0npFmG -PRf7sN9+LeMCgYAGkDUDL2ROoB6gRa/7Vdx90hKMoXJkYgwLA4gJ2pDlR3A3c/Lw -5KQJyxmG3zm/IqeQF6be6QesZA30mT4peV2rGHbP2WH/s6fKReNelSy1VQJEWk8x -tGUgV4gwDwN5nLV4TjYlOrq+bJqvpmLhCC8bmj0jVQosYqSRl3cuICasnQKBgGlV -VO/Xb1su1EyWPK5qxRIeSxZOTYw2sMB01nbgxCqge0M2fvA6/hQ5ZlwY0cIEgits -YlcSMsMq/TAAANxz1vbaupUhlSMbZcsBvNV0Nk9c4vr2Wxm7hsJF9u66IEMvQUp2 -pkjiMxfR9CHzF4orr9EcHI5EQ0Grbq5kwFKEfoRbAoGAcWoFPILeJOlp2yW/Ds3E -g2fQdI9BAamtEZEaslJmZMmsDTg5ACPcDkOSFEQIaJ7wLPXeZy74FVk/NrY5F8Gz -bjX9OD/xzwp852yW5L9r62vYJakAlXef5jI6CFdYKDDCcarU0S7W5k6kq9n+wrBR -i1NklYmUAMr2q59uJA5zsic= ------END PRIVATE KEY----- diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs deleted file mode 100644 index ecd5c9ffb..000000000 --- a/tests/test_httpserver.rs +++ /dev/null @@ -1,146 +0,0 @@ -use net2::TcpBuilder; -use std::sync::mpsc; -use std::{net, thread, time::Duration}; - -#[cfg(feature = "openssl")] -use open_ssl::ssl::SslAcceptorBuilder; - -use actix_web::{web, App, HttpResponse, HttpServer}; - -fn unused_addr() -> net::SocketAddr { - let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = TcpBuilder::new_v4().unwrap(); - socket.bind(&addr).unwrap(); - socket.reuse_address(true).unwrap(); - let tcp = socket.to_tcp_listener().unwrap(); - tcp.local_addr().unwrap() -} - -#[cfg(unix)] -#[actix_rt::test] -async fn test_start() { - let addr = unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(move || { - let sys = actix_rt::System::new("test"); - - let srv = HttpServer::new(|| { - App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok().body("test"))), - ) - }) - .workers(1) - .backlog(1) - .maxconn(10) - .maxconnrate(10) - .keep_alive(10) - .client_timeout(5000) - .client_shutdown(0) - .server_hostname("localhost") - .system_exit() - .disable_signals() - .bind(format!("{}", addr)) - .unwrap() - .run(); - - let _ = tx.send((srv, actix_rt::System::current())); - let _ = sys.run(); - }); - let (srv, sys) = rx.recv().unwrap(); - - #[cfg(feature = "client")] - { - use actix_http::client; - - let client = awc::Client::build() - .connector( - client::Connector::new() - .timeout(Duration::from_millis(100)) - .finish(), - ) - .finish(); - - let host = format!("http://{}", addr); - let response = client.get(host.clone()).send().await.unwrap(); - assert!(response.status().is_success()); - } - - // stop - let _ = srv.stop(false); - - thread::sleep(Duration::from_millis(100)); - let _ = sys.stop(); -} - -#[cfg(feature = "openssl")] -fn ssl_acceptor() -> std::io::Result { - use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - Ok(builder) -} - -#[actix_rt::test] -#[cfg(feature = "openssl")] -async fn test_start_ssl() { - use actix_web::HttpRequest; - - let addr = unused_addr(); - let (tx, rx) = mpsc::channel(); - - thread::spawn(move || { - let sys = actix_rt::System::new("test"); - let builder = ssl_acceptor().unwrap(); - - let srv = HttpServer::new(|| { - App::new().service(web::resource("/").route(web::to(|req: HttpRequest| { - assert!(req.app_config().secure()); - HttpResponse::Ok().body("test") - }))) - }) - .workers(1) - .shutdown_timeout(1) - .system_exit() - .disable_signals() - .bind_openssl(format!("{}", addr), builder) - .unwrap() - .run(); - - let _ = tx.send((srv, actix_rt::System::current())); - let _ = sys.run(); - }); - let (srv, sys) = rx.recv().unwrap(); - - use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - - let client = awc::Client::build() - .connector( - awc::Connector::new() - .ssl(builder.build()) - .timeout(Duration::from_millis(100)) - .finish(), - ) - .finish(); - - let host = format!("https://{}", addr); - let response = client.get(host.clone()).send().await.unwrap(); - assert!(response.status().is_success()); - - // stop - let _ = srv.stop(false); - - thread::sleep(Duration::from_millis(100)); - let _ = sys.stop(); -} diff --git a/tests/test_server.rs b/tests/test_server.rs deleted file mode 100644 index 1916b372c..000000000 --- a/tests/test_server.rs +++ /dev/null @@ -1,891 +0,0 @@ -use std::io::{Read, Write}; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use actix_http::http::header::{ - ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, - TRANSFER_ENCODING, -}; -use brotli2::write::{BrotliDecoder, BrotliEncoder}; -use bytes::Bytes; -use flate2::read::GzDecoder; -use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; -use flate2::Compression; -use futures::{ready, Future}; -use rand::{distributions::Alphanumeric, Rng}; - -use actix_web::dev::BodyEncoding; -use actix_web::middleware::Compress; -use actix_web::{dev, test, web, App, Error, HttpResponse}; - -const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World \ - Hello World Hello World Hello World Hello World Hello World"; - -struct TestBody { - data: Bytes, - chunk_size: usize, - delay: actix_rt::time::Delay, -} - -impl TestBody { - fn new(data: Bytes, chunk_size: usize) -> Self { - TestBody { - data, - chunk_size, - delay: actix_rt::time::delay_for(std::time::Duration::from_millis(10)), - } - } -} - -impl futures::Stream for TestBody { - type Item = Result; - - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - ready!(Pin::new(&mut self.delay).poll(cx)); - - self.delay = actix_rt::time::delay_for(std::time::Duration::from_millis(10)); - let chunk_size = std::cmp::min(self.chunk_size, self.data.len()); - let chunk = self.data.split_to(chunk_size); - if chunk.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(chunk))) - } - } -} - -#[actix_rt::test] -async fn test_body() { - let srv = test::start(|| { - App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) - }); - - let mut response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_body_gzip() { - let srv = test::start_with(test::config().h1(), || { - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) - }); - - let mut response = srv - .get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_body_gzip2() { - let srv = test::start_with(test::config().h1(), || { - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| { - HttpResponse::Ok().body(STR).into_body::() - }))) - }); - - let mut response = srv - .get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_body_encoding_override() { - let srv = test::start_with(test::config().h1(), || { - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| { - HttpResponse::Ok() - .encoding(ContentEncoding::Deflate) - .body(STR) - }))) - .service(web::resource("/raw").route(web::to(|| { - let body = actix_web::dev::Body::Bytes(STR.into()); - let mut response = - HttpResponse::with_body(actix_web::http::StatusCode::OK, body); - - response.encoding(ContentEncoding::Deflate); - - response - }))) - }); - - // Builder - let mut response = srv - .get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "deflate") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - - // decode - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); - - // Raw Response - let mut response = srv - .request(actix_web::http::Method::GET, srv.url("/raw")) - .no_decompress() - .header(ACCEPT_ENCODING, "deflate") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - - // decode - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_body_gzip_large() { - let data = STR.repeat(10); - let srv_data = data.clone(); - - let srv = test::start_with(test::config().h1(), move || { - let data = srv_data.clone(); - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service( - web::resource("/") - .route(web::to(move || HttpResponse::Ok().body(data.clone()))), - ) - }); - - let mut response = srv - .get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from(data)); -} - -#[actix_rt::test] -async fn test_body_gzip_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(70_000) - .collect::(); - let srv_data = data.clone(); - - let srv = test::start_with(test::config().h1(), move || { - let data = srv_data.clone(); - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service( - web::resource("/") - .route(web::to(move || HttpResponse::Ok().body(data.clone()))), - ) - }); - - let mut response = srv - .get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(dec.len(), data.len()); - assert_eq!(Bytes::from(dec), Bytes::from(data)); -} - -#[actix_rt::test] -async fn test_body_chunked_implicit() { - let srv = test::start_with(test::config().h1(), || { - App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::get().to(move || { - HttpResponse::Ok() - .streaming(TestBody::new(Bytes::from_static(STR.as_ref()), 24)) - }))) - }); - - let mut response = srv - .get("/") - .no_decompress() - .header(ACCEPT_ENCODING, "gzip") - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - assert_eq!( - response.headers().get(TRANSFER_ENCODING).unwrap(), - &b"chunked"[..] - ); - - // read response - let bytes = response.body().await.unwrap(); - - // decode - let mut e = GzDecoder::new(&bytes[..]); - let mut dec = Vec::new(); - e.read_to_end(&mut dec).unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_body_br_streaming() { - let srv = test::start_with(test::config().h1(), || { - App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || { - HttpResponse::Ok() - .streaming(TestBody::new(Bytes::from_static(STR.as_ref()), 24)) - })), - ) - }); - - let mut response = srv - .get("/") - .header(ACCEPT_ENCODING, "br") - .no_decompress() - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - println!("TEST: {:?}", bytes.len()); - - // decode br - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - println!("T: {:?}", Bytes::copy_from_slice(&dec)); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_head_binary() { - let srv = test::start_with(test::config().h1(), || { - App::new().service(web::resource("/").route( - web::head().to(move || HttpResponse::Ok().content_length(100).body(STR)), - )) - }); - - let mut response = srv.head("/").send().await.unwrap(); - assert!(response.status().is_success()); - - { - let len = response.headers().get(CONTENT_LENGTH).unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = response.body().await.unwrap(); - assert!(bytes.is_empty()); -} - -#[actix_rt::test] -async fn test_no_chunking() { - let srv = test::start_with(test::config().h1(), || { - App::new().service(web::resource("/").route(web::to(move || { - HttpResponse::Ok() - .no_chunking() - .content_length(STR.len() as u64) - .streaming(TestBody::new(Bytes::from_static(STR.as_ref()), 24)) - }))) - }); - - let mut response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - assert!(!response.headers().contains_key(TRANSFER_ENCODING)); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_body_deflate() { - let srv = test::start_with(test::config().h1(), || { - App::new() - .wrap(Compress::new(ContentEncoding::Deflate)) - .service( - web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR))), - ) - }); - - // client request - let mut response = srv - .get("/") - .header(ACCEPT_ENCODING, "deflate") - .no_decompress() - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - - let mut e = ZlibDecoder::new(Vec::new()); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_body_brotli() { - let srv = test::start_with(test::config().h1(), || { - App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR))), - ) - }); - - // client request - let mut response = srv - .get("/") - .header(ACCEPT_ENCODING, "br") - .no_decompress() - .send() - .await - .unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - - // decode brotli - let mut e = BrotliDecoder::new(Vec::with_capacity(2048)); - e.write_all(bytes.as_ref()).unwrap(); - let dec = e.finish().unwrap(); - assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_encoding() { - let srv = test::start_with(test::config().h1(), || { - App::new().wrap(Compress::default()).service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_gzip_encoding() { - let srv = test::start_with(test::config().h1(), || { - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_gzip_encoding_large() { - let data = STR.repeat(10); - let srv = test::start_with(test::config().h1(), || { - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[actix_rt::test] -async fn test_reading_gzip_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(60_000) - .collect::(); - - let srv = test::start_with(test::config().h1(), || { - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) - }); - - // client request - let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - let request = srv - .post("/") - .header(CONTENT_ENCODING, "gzip") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[actix_rt::test] -async fn test_reading_deflate_encoding() { - let srv = test::start_with(test::config().h1(), || { - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "deflate") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_reading_deflate_encoding_large() { - let data = STR.repeat(10); - let srv = test::start_with(test::config().h1(), || { - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "deflate") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[actix_rt::test] -async fn test_reading_deflate_encoding_large_random() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - let srv = test::start_with(test::config().h1(), || { - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) - }); - - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "deflate") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -#[actix_rt::test] -async fn test_brotli_encoding() { - let srv = test::start_with(test::config().h1(), || { - App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), - ) - }); - - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "br") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - -#[actix_rt::test] -async fn test_brotli_encoding_large() { - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(320_000) - .collect::(); - - let srv = test::start_with(test::config().h1(), || { - App::new().service( - web::resource("/") - .app_data(web::PayloadConfig::new(320_000)) - .route(web::to(move |body: Bytes| { - HttpResponse::Ok().streaming(TestBody::new(body, 10240)) - })), - ) - }); - - let mut e = BrotliEncoder::new(Vec::new(), 5); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let request = srv - .post("/") - .header(CONTENT_ENCODING, "br") - .send_body(enc.clone()); - let mut response = request.await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().limit(320_000).await.unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(feature = "openssl")] -#[actix_rt::test] -async fn test_brotli_encoding_large_openssl() { - // load ssl keys - use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - - let data = STR.repeat(10); - let srv = test::start_with(test::config().openssl(builder.build()), move || { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { - HttpResponse::Ok() - .encoding(actix_web::http::ContentEncoding::Identity) - .body(bytes) - }))) - }); - - // body - let mut e = BrotliEncoder::new(Vec::new(), 3); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let mut response = srv - .post("/") - .header(actix_web::http::header::CONTENT_ENCODING, "br") - .send_body(enc) - .await - .unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes, Bytes::from(data)); -} - -#[cfg(all(feature = "rustls", feature = "openssl"))] -#[actix_rt::test] -async fn test_reading_deflate_encoding_large_random_rustls() { - use rust_tls::internal::pemfile::{certs, pkcs8_private_keys}; - use rust_tls::{NoClientAuth, ServerConfig}; - use std::fs::File; - use std::io::BufReader; - - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .collect::(); - - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - - let srv = test::start_with(test::config().rustls(config), || { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { - HttpResponse::Ok() - .encoding(actix_web::http::ContentEncoding::Identity) - .body(bytes) - }))) - }); - - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); - - // client request - let req = srv - .post("/") - .header(actix_web::http::header::CONTENT_ENCODING, "deflate") - .send_stream(TestBody::new(Bytes::from(enc), 1024)); - - let mut response = req.await.unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); -} - -// #[test] -// fn test_server_cookies() { -// use actix_web::http; - -// let srv = test::TestServer::with_factory(|| { -// App::new().resource("/", |r| { -// r.f(|_| { -// HttpResponse::Ok() -// .cookie( -// http::CookieBuilder::new("first", "first_value") -// .http_only(true) -// .finish(), -// ) -// .cookie(http::Cookie::new("second", "first_value")) -// .cookie(http::Cookie::new("second", "second_value")) -// .finish() -// }) -// }) -// }); - -// let first_cookie = http::CookieBuilder::new("first", "first_value") -// .http_only(true) -// .finish(); -// let second_cookie = http::Cookie::new("second", "second_value"); - -// let request = srv.get("/").finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); - -// let cookies = response.cookies().expect("To have cookies"); -// assert_eq!(cookies.len(), 2); -// if cookies[0] == first_cookie { -// assert_eq!(cookies[1], second_cookie); -// } else { -// assert_eq!(cookies[0], second_cookie); -// assert_eq!(cookies[1], first_cookie); -// } - -// let first_cookie = first_cookie.to_string(); -// let second_cookie = second_cookie.to_string(); -// //Check that we have exactly two instances of raw cookie headers -// let cookies = response -// .headers() -// .get_all(http::header::SET_COOKIE) -// .iter() -// .map(|header| header.to_str().expect("To str").to_string()) -// .collect::>(); -// assert_eq!(cookies.len(), 2); -// if cookies[0] == first_cookie { -// assert_eq!(cookies[1], second_cookie); -// } else { -// assert_eq!(cookies[0], second_cookie); -// assert_eq!(cookies[1], first_cookie); -// } -// } - -#[actix_rt::test] -async fn test_slow_request() { - use std::net; - - let srv = test::start_with(test::config().client_timeout(200), || { - App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))) - }); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); - - let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); - let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); - let mut data = String::new(); - let _ = stream.read_to_string(&mut data); - assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); -} - -// #[cfg(feature = "openssl")] -// #[actix_rt::test] -// async fn test_ssl_handshake_timeout() { -// use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; -// use std::net; - -// // load ssl keys -// let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); -// builder -// .set_private_key_file("tests/key.pem", SslFiletype::PEM) -// .unwrap(); -// builder -// .set_certificate_chain_file("tests/cert.pem") -// .unwrap(); - -// let srv = test::start_with(test::config().openssl(builder.build()), || { -// App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))) -// }); - -// let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); -// let mut data = String::new(); -// let _ = stream.read_to_string(&mut data); -// assert!(data.is_empty()); -// }