├── .clippy.toml ├── .codecov.yml ├── .cspell.yml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── bench.yml │ ├── ci-post-merge.yml │ ├── ci.yml │ ├── coverage.yml │ └── lint.yml ├── .gitignore ├── .prettierrc.yml ├── .rustfmt.toml ├── .taplo.toml ├── CHANGES.md ├── CODE_OF_CONDUCT.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── actix-files ├── CHANGES.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples │ └── guarded-listing.rs ├── src │ ├── chunked.rs │ ├── directory.rs │ ├── encoding.rs │ ├── error.rs │ ├── files.rs │ ├── lib.rs │ ├── named.rs │ ├── path_buf.rs │ ├── range.rs │ └── service.rs └── tests │ ├── encoding.rs │ ├── fixtures │ └── guards │ │ ├── first │ │ └── index.txt │ │ └── second │ │ └── index.txt │ ├── guard.rs │ ├── symlink-test.png │ ├── test space.binary │ ├── test.binary │ ├── test.js │ ├── test.png │ ├── traversal.rs │ └── utf8.txt ├── actix-http-test ├── CHANGES.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src │ └── lib.rs ├── actix-http ├── CHANGES.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches │ ├── date-formatting.rs │ └── response-body-compression.rs ├── examples │ ├── actix-web.rs │ ├── bench.rs │ ├── echo.rs │ ├── echo2.rs │ ├── h2c-detect.rs │ ├── h2spec.rs │ ├── hello-world.rs │ ├── streaming-error.rs │ ├── tls_rustls.rs │ └── ws.rs ├── src │ ├── body │ │ ├── body_stream.rs │ │ ├── boxed.rs │ │ ├── either.rs │ │ ├── message_body.rs │ │ ├── mod.rs │ │ ├── none.rs │ │ ├── size.rs │ │ ├── sized_stream.rs │ │ └── utils.rs │ ├── builder.rs │ ├── config.rs │ ├── date.rs │ ├── encoding │ │ ├── decoder.rs │ │ ├── encoder.rs │ │ └── mod.rs │ ├── error.rs │ ├── extensions.rs │ ├── h1 │ │ ├── chunked.rs │ │ ├── client.rs │ │ ├── codec.rs │ │ ├── decoder.rs │ │ ├── dispatcher.rs │ │ ├── dispatcher_tests.rs │ │ ├── encoder.rs │ │ ├── expect.rs │ │ ├── mod.rs │ │ ├── payload.rs │ │ ├── service.rs │ │ ├── timer.rs │ │ ├── upgrade.rs │ │ └── utils.rs │ ├── h2 │ │ ├── dispatcher.rs │ │ ├── mod.rs │ │ └── service.rs │ ├── header │ │ ├── as_name.rs │ │ ├── common.rs │ │ ├── into_pair.rs │ │ ├── into_value.rs │ │ ├── map.rs │ │ ├── mod.rs │ │ ├── shared │ │ │ ├── charset.rs │ │ │ ├── content_encoding.rs │ │ │ ├── extended.rs │ │ │ ├── http_date.rs │ │ │ ├── mod.rs │ │ │ ├── quality.rs │ │ │ └── quality_item.rs │ │ └── utils.rs │ ├── helpers.rs │ ├── http_message.rs │ ├── keep_alive.rs │ ├── lib.rs │ ├── message.rs │ ├── notify_on_drop.rs │ ├── payload.rs │ ├── requests │ │ ├── head.rs │ │ ├── mod.rs │ │ └── request.rs │ ├── responses │ │ ├── builder.rs │ │ ├── head.rs │ │ ├── mod.rs │ │ └── response.rs │ ├── service.rs │ ├── test.rs │ └── ws │ │ ├── codec.rs │ │ ├── dispatcher.rs │ │ ├── frame.rs │ │ ├── mask.rs │ │ ├── mod.rs │ │ └── proto.rs └── tests │ ├── test.binary │ ├── test.png │ ├── test_client.rs │ ├── test_h2_timer.rs │ ├── test_openssl.rs │ ├── test_rustls.rs │ ├── test_server.rs │ └── test_ws.rs ├── actix-multipart-derive ├── CHANGES.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── src │ └── lib.rs └── tests │ ├── trybuild.rs │ └── trybuild │ ├── all-required.rs │ ├── deny-duplicates.rs │ ├── deny-parse-fail.rs │ ├── deny-parse-fail.stderr │ ├── deny-unknown.rs │ ├── optional-and-list.rs │ ├── rename.rs │ ├── size-limit-parse-fail.rs │ ├── size-limit-parse-fail.stderr │ └── size-limits.rs ├── actix-multipart ├── CHANGES.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples │ └── form.rs └── src │ ├── error.rs │ ├── extractor.rs │ ├── field.rs │ ├── form │ ├── bytes.rs │ ├── json.rs │ ├── mod.rs │ ├── tempfile.rs │ └── text.rs │ ├── lib.rs │ ├── multipart.rs │ ├── payload.rs │ ├── safety.rs │ └── test.rs ├── actix-router ├── CHANGES.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches │ ├── quoter.rs │ └── router.rs └── src │ ├── de.rs │ ├── lib.rs │ ├── path.rs │ ├── pattern.rs │ ├── quoter.rs │ ├── regex_set.rs │ ├── resource.rs │ ├── resource_path.rs │ ├── router.rs │ └── url.rs ├── actix-test ├── CHANGES.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src │ └── lib.rs ├── actix-web-actors ├── CHANGES.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── src │ ├── context.rs │ ├── lib.rs │ └── ws.rs └── tests │ └── test_ws.rs ├── actix-web-codegen ├── CHANGES.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── src │ ├── lib.rs │ ├── route.rs │ └── scope.rs └── tests │ ├── routes.rs │ ├── scopes.rs │ ├── trybuild.rs │ └── trybuild │ ├── docstring-ok.rs │ ├── route-custom-lowercase.rs │ ├── route-custom-lowercase.stderr │ ├── route-custom-method.rs │ ├── route-duplicate-method-fail.rs │ ├── route-duplicate-method-fail.stderr │ ├── route-malformed-path-fail.rs │ ├── route-malformed-path-fail.stderr │ ├── route-missing-method-fail.rs │ ├── route-missing-method-fail.stderr │ ├── route-ok.rs │ ├── routes-missing-args-fail.rs │ ├── routes-missing-args-fail.stderr │ ├── routes-missing-method-fail.rs │ ├── routes-missing-method-fail.stderr │ ├── routes-ok.rs │ ├── scope-invalid-args.rs │ ├── scope-invalid-args.stderr │ ├── scope-missing-args.rs │ ├── scope-missing-args.stderr │ ├── scope-on-handler.rs │ ├── scope-on-handler.stderr │ ├── scope-trailing-slash.rs │ ├── scope-trailing-slash.stderr │ ├── simple-fail.rs │ ├── simple-fail.stderr │ ├── simple.rs │ └── test-runtime.rs ├── actix-web ├── CHANGES.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── MIGRATION-0.x.md ├── MIGRATION-1.0.md ├── MIGRATION-2.0.md ├── MIGRATION-3.0.md ├── MIGRATION-4.0.md ├── README.md ├── benches │ ├── responder.rs │ ├── server.rs │ └── service.rs ├── examples │ ├── README.md │ ├── basic.rs │ ├── from_fn.rs │ ├── macroless.rs │ ├── middleware_from_fn.rs │ ├── on-connect.rs │ ├── uds.rs │ └── worker-cpu-pin.rs ├── src │ ├── app.rs │ ├── app_service.rs │ ├── config.rs │ ├── data.rs │ ├── dev.rs │ ├── error │ │ ├── error.rs │ │ ├── internal.rs │ │ ├── macros.rs │ │ ├── mod.rs │ │ └── response_error.rs │ ├── extract.rs │ ├── guard │ │ ├── acceptable.rs │ │ ├── host.rs │ │ └── mod.rs │ ├── handler.rs │ ├── helpers.rs │ ├── http │ │ ├── header │ │ │ ├── accept.rs │ │ │ ├── accept_charset.rs │ │ │ ├── accept_encoding.rs │ │ │ ├── accept_language.rs │ │ │ ├── allow.rs │ │ │ ├── any_or_some.rs │ │ │ ├── cache_control.rs │ │ │ ├── content_disposition.rs │ │ │ ├── content_language.rs │ │ │ ├── content_length.rs │ │ │ ├── content_range.rs │ │ │ ├── content_type.rs │ │ │ ├── date.rs │ │ │ ├── encoding.rs │ │ │ ├── entity.rs │ │ │ ├── etag.rs │ │ │ ├── expires.rs │ │ │ ├── if_match.rs │ │ │ ├── if_modified_since.rs │ │ │ ├── if_none_match.rs │ │ │ ├── if_range.rs │ │ │ ├── if_unmodified_since.rs │ │ │ ├── last_modified.rs │ │ │ ├── macros.rs │ │ │ ├── mod.rs │ │ │ ├── preference.rs │ │ │ └── range.rs │ │ └── mod.rs │ ├── info.rs │ ├── lib.rs │ ├── middleware │ │ ├── authors-guide.md │ │ ├── compat.rs │ │ ├── compress.rs │ │ ├── condition.rs │ │ ├── default_headers.rs │ │ ├── err_handlers.rs │ │ ├── from_fn.rs │ │ ├── identity.rs │ │ ├── logger.rs │ │ ├── mod.rs │ │ └── normalize.rs │ ├── redirect.rs │ ├── request.rs │ ├── request_data.rs │ ├── resource.rs │ ├── response │ │ ├── builder.rs │ │ ├── customize_responder.rs │ │ ├── http_codes.rs │ │ ├── mod.rs │ │ ├── responder.rs │ │ └── response.rs │ ├── rmap.rs │ ├── route.rs │ ├── rt.rs │ ├── scope.rs │ ├── server.rs │ ├── service.rs │ ├── test │ │ ├── mod.rs │ │ ├── test_request.rs │ │ ├── test_services.rs │ │ └── test_utils.rs │ ├── thin_data.rs │ ├── types │ │ ├── either.rs │ │ ├── form.rs │ │ ├── header.rs │ │ ├── html.rs │ │ ├── json.rs │ │ ├── mod.rs │ │ ├── path.rs │ │ ├── payload.rs │ │ ├── query.rs │ │ └── readlines.rs │ └── web.rs └── tests │ ├── compression.rs │ ├── fixtures │ ├── lorem.txt │ ├── lorem.txt.br │ ├── lorem.txt.gz │ ├── lorem.txt.xz │ └── lorem.txt.zst │ ├── test-macro-import-conflict.rs │ ├── test_error_propagation.rs │ ├── test_httpserver.rs │ ├── test_server.rs │ ├── test_weird_poll.rs │ ├── utils.rs │ └── weird_poll.rs ├── awc ├── CHANGES.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples │ └── client.rs ├── src │ ├── any_body.rs │ ├── builder.rs │ ├── client │ │ ├── config.rs │ │ ├── connection.rs │ │ ├── connector.rs │ │ ├── error.rs │ │ ├── h1proto.rs │ │ ├── h2proto.rs │ │ ├── mod.rs │ │ └── pool.rs │ ├── connect.rs │ ├── error.rs │ ├── frozen.rs │ ├── lib.rs │ ├── middleware │ │ ├── mod.rs │ │ └── redirect.rs │ ├── request.rs │ ├── responses │ │ ├── json_body.rs │ │ ├── mod.rs │ │ ├── read_body.rs │ │ ├── response.rs │ │ └── response_body.rs │ ├── sender.rs │ ├── test.rs │ └── ws.rs └── tests │ ├── test_client.rs │ ├── test_connector.rs │ ├── test_rustls_client.rs │ ├── test_ssl_client.rs │ ├── test_ws.rs │ └── utils.rs ├── docs └── graphs │ ├── .gitignore │ ├── README.md │ ├── net-only.dot │ ├── web-focus.dot │ └── web-only.dot ├── justfile └── scripts ├── bump ├── ci-test ├── free-disk-space.sh └── unreleased /.clippy.toml: -------------------------------------------------------------------------------- 1 | disallowed-names = [ 2 | "..", 3 | "e", # no single letter error bindings 4 | ] 5 | disallowed-methods = [ 6 | { path = "std::cell::RefCell::default()", reason = "prefer explicit inner type default" }, 7 | { path = "std::rc::Rc::default()", reason = "prefer explicit inner type default" }, 8 | ] 9 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | threshold: 100% # make CI green 8 | patch: 9 | default: 10 | threshold: 100% # make CI green 11 | 12 | ignore: # ignore code coverage on following paths 13 | - "**/tests" 14 | - "**/benches" 15 | - "**/examples" 16 | -------------------------------------------------------------------------------- /.cspell.yml: -------------------------------------------------------------------------------- 1 | version: "0.2" 2 | words: 3 | - actix 4 | - addrs 5 | - bytestring 6 | - httparse 7 | - msrv 8 | - realip 9 | - rustls 10 | - rustup 11 | - serde 12 | - zstd 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [robjtede] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a bug report. 4 | --- 5 | 6 | Your issue may already be reported! Please search on the [Actix Web issue tracker](https://github.com/actix/actix-web/issues) before creating one. 7 | 8 | ## Expected Behavior 9 | 10 | 11 | 12 | 13 | ## Current Behavior 14 | 15 | 16 | 17 | 18 | ## Possible Solution 19 | 20 | 21 | 22 | 23 | ## Steps to Reproduce (for bugs) 24 | 25 | 26 | 27 | 28 | 1. 29 | 2. 30 | 3. 31 | 4. 32 | 33 | ## Context 34 | 35 | 36 | 37 | 38 | ## Your Environment 39 | 40 | 41 | 42 | - Rust Version (I.e, output of `rustc -V`): 43 | - Actix Web Version: 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Actix Discord 4 | url: https://discord.gg/NWpN5mmg3x 5 | about: Actix developer discussion and community chat 6 | - name: GitHub Discussions 7 | url: https://github.com/actix/actix-web/discussions 8 | about: Actix Web Q&A 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## PR Type 5 | 6 | 7 | 8 | 9 | PR_TYPE 10 | 11 | ## PR Checklist 12 | 13 | 14 | 15 | 16 | - [ ] Tests for the changes have been added / updated. 17 | - [ ] Documentation comments have been added / updated. 18 | - [ ] A changelog entry has been made for the appropriate packages. 19 | - [ ] Format code with the latest stable rustfmt. 20 | - [ ] (Team) Label with affected crates and semver status. 21 | 22 | ## Overview 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: cargo 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | versioning-strategy: lockfile-only 12 | -------------------------------------------------------------------------------- /.github/workflows/bench.yml: -------------------------------------------------------------------------------- 1 | name: Benchmark 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | permissions: 8 | contents: read 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | check_benchmark: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Install Rust 22 | run: | 23 | rustup set profile minimal 24 | rustup install nightly 25 | rustup override set nightly 26 | 27 | - name: Check benchmark 28 | run: cargo bench --bench=server -- --sample-size=15 29 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | permissions: 8 | contents: read 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | coverage: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Install Rust (nightly) 21 | uses: actions-rust-lang/setup-rust-toolchain@v1.12.0 22 | with: 23 | toolchain: nightly 24 | components: llvm-tools 25 | 26 | - name: Install just, cargo-llvm-cov, cargo-nextest 27 | uses: taiki-e/install-action@v2.52.1 28 | with: 29 | tool: just,cargo-llvm-cov,cargo-nextest 30 | 31 | - name: Generate code coverage 32 | run: just test-coverage-codecov 33 | 34 | - name: Upload coverage to Codecov 35 | uses: codecov/codecov-action@v5.4.3 36 | with: 37 | files: codecov.json 38 | fail_ci_if_error: true 39 | env: 40 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 41 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | 7 | permissions: 8 | contents: read 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | fmt: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Install Rust (nightly) 21 | uses: actions-rust-lang/setup-rust-toolchain@v1.12.0 22 | with: 23 | toolchain: nightly 24 | components: rustfmt 25 | 26 | - name: Check with Rustfmt 27 | run: cargo fmt --all -- --check 28 | 29 | clippy: 30 | permissions: 31 | contents: read 32 | checks: write # to add clippy checks to PR diffs 33 | 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v4 37 | 38 | - name: Install Rust 39 | uses: actions-rust-lang/setup-rust-toolchain@v1.12.0 40 | with: 41 | components: clippy 42 | 43 | - name: Check with Clippy 44 | uses: giraffate/clippy-action@v1.0.1 45 | with: 46 | reporter: github-pr-check 47 | github_token: ${{ secrets.GITHUB_TOKEN }} 48 | clippy_flags: >- 49 | --workspace --all-features --tests --examples --bins -- 50 | -A unknown_lints -D clippy::todo -D clippy::dbg_macro 51 | 52 | lint-docs: 53 | runs-on: ubuntu-latest 54 | steps: 55 | - uses: actions/checkout@v4 56 | 57 | - name: Install Rust (nightly) 58 | uses: actions-rust-lang/setup-rust-toolchain@v1.12.0 59 | with: 60 | toolchain: nightly 61 | components: rust-docs 62 | 63 | - name: Check for broken intra-doc links 64 | env: 65 | RUSTDOCFLAGS: -D warnings 66 | run: cargo +nightly doc --no-deps --workspace --all-features 67 | 68 | check-external-types: 69 | if: false # rustdoc mismatch currently 70 | runs-on: ubuntu-latest 71 | steps: 72 | - uses: actions/checkout@v4 73 | 74 | - name: Install Rust (${{ vars.RUST_VERSION_EXTERNAL_TYPES }}) 75 | uses: actions-rust-lang/setup-rust-toolchain@v1.12.0 76 | with: 77 | toolchain: ${{ vars.RUST_VERSION_EXTERNAL_TYPES }} 78 | 79 | - name: Install just 80 | uses: taiki-e/install-action@v2.52.1 81 | with: 82 | tool: just 83 | 84 | - name: Install cargo-check-external-types 85 | uses: taiki-e/cache-cargo-install-action@v2.1.1 86 | with: 87 | tool: cargo-check-external-types 88 | 89 | - name: check external types 90 | run: just check-external-types-all +${{ vars.RUST_VERSION_EXTERNAL_TYPES }} 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | guide/build/ 3 | /gh-pages 4 | 5 | *.so 6 | *.out 7 | *.pyc 8 | *.pid 9 | *.sock 10 | *~ 11 | .DS_Store 12 | 13 | # These are backup files generated by rustfmt 14 | **/*.rs.bk 15 | 16 | # Configuration directory generated by CLion 17 | .idea 18 | 19 | # Configuration directory generated by VSCode 20 | .vscode 21 | 22 | # code coverage 23 | /lcov.info 24 | /codecov.json 25 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | overrides: 2 | - files: "*.md" 3 | options: 4 | printWidth: 9999 5 | proseWrap: never 6 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "StdExternalCrate" 2 | imports_granularity = "Crate" 3 | use_field_init_shorthand = true 4 | -------------------------------------------------------------------------------- /.taplo.toml: -------------------------------------------------------------------------------- 1 | exclude = ["target/*"] 2 | include = ["**/*.toml"] 3 | 4 | [formatting] 5 | column_width = 100 6 | align_comments = false 7 | 8 | [[rule]] 9 | include = ["**/Cargo.toml"] 10 | keys = ["features"] 11 | formatting.column_width = 105 12 | formatting.reorder_keys = false 13 | 14 | [[rule]] 15 | include = ["**/Cargo.toml"] 16 | keys = [ 17 | "dependencies", 18 | "*-dependencies", 19 | "workspace.dependencies", 20 | "workspace.*-dependencies", 21 | "target.*.dependencies", 22 | "target.*.*-dependencies", 23 | ] 24 | formatting.column_width = 120 25 | formatting.reorder_keys = true 26 | 27 | [[rule]] 28 | include = ["**/Cargo.toml"] 29 | keys = [ 30 | "dependencies.*", 31 | "*-dependencies.*", 32 | "workspace.dependencies.*", 33 | "workspace.*-dependencies.*", 34 | "target.*.dependencies", 35 | "target.*.*-dependencies", 36 | ] 37 | formatting.column_width = 120 38 | formatting.reorder_keys = false 39 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Changelogs are kept separately for each crate in this repo. 4 | 5 | Actix Web changelog [is now here →](./actix-web/CHANGES.md). 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "actix-files", 5 | "actix-http-test", 6 | "actix-http", 7 | "actix-multipart", 8 | "actix-multipart-derive", 9 | "actix-router", 10 | "actix-test", 11 | "actix-web-actors", 12 | "actix-web-codegen", 13 | "actix-web", 14 | "awc", 15 | ] 16 | 17 | [workspace.package] 18 | homepage = "https://actix.rs" 19 | repository = "https://github.com/actix/actix-web" 20 | license = "MIT OR Apache-2.0" 21 | edition = "2021" 22 | rust-version = "1.75" 23 | 24 | [profile.dev] 25 | # Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much. 26 | debug = 0 27 | 28 | [profile.release] 29 | lto = true 30 | opt-level = 3 31 | codegen-units = 1 32 | 33 | [patch.crates-io] 34 | actix-files = { path = "actix-files" } 35 | actix-http = { path = "actix-http" } 36 | actix-http-test = { path = "actix-http-test" } 37 | actix-multipart = { path = "actix-multipart" } 38 | actix-multipart-derive = { path = "actix-multipart-derive" } 39 | actix-router = { path = "actix-router" } 40 | actix-test = { path = "actix-test" } 41 | actix-web = { path = "actix-web" } 42 | actix-web-actors = { path = "actix-web-actors" } 43 | actix-web-codegen = { path = "actix-web-codegen" } 44 | awc = { path = "awc" } 45 | 46 | # uncomment for quick testing against local actix-net repo 47 | # actix-service = { path = "../actix-net/actix-service" } 48 | # actix-macros = { path = "../actix-net/actix-macros" } 49 | # actix-rt = { path = "../actix-net/actix-rt" } 50 | # actix-codec = { path = "../actix-net/actix-codec" } 51 | # actix-utils = { path = "../actix-net/actix-utils" } 52 | # actix-tls = { path = "../actix-net/actix-tls" } 53 | # actix-server = { path = "../actix-net/actix-server" } 54 | 55 | [workspace.lints.rust] 56 | rust_2018_idioms = { level = "deny" } 57 | future_incompatible = { level = "deny" } 58 | nonstandard_style = { level = "deny" } 59 | 60 | [workspace.lints.clippy] 61 | # clone_on_ref_ptr = { level = "deny" } 62 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-NOW Actix Team 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | actix-web/README.md -------------------------------------------------------------------------------- /actix-files/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix-files" 3 | version = "0.6.6" 4 | authors = ["Nikolay Kim ", "Rob Ede "] 5 | description = "Static file serving for Actix Web" 6 | keywords = ["actix", "http", "async", "futures"] 7 | homepage = "https://actix.rs" 8 | repository = "https://github.com/actix/actix-web" 9 | categories = ["asynchronous", "web-programming::http-server"] 10 | license = "MIT OR Apache-2.0" 11 | edition = "2021" 12 | 13 | [package.metadata.cargo_check_external_types] 14 | allowed_external_types = ["actix_http::*", "actix_service::*", "actix_web::*", "http::*", "mime::*"] 15 | 16 | [features] 17 | experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] 18 | 19 | [dependencies] 20 | actix-http = "3" 21 | actix-service = "2" 22 | actix-utils = "3" 23 | actix-web = { version = "4", default-features = false } 24 | 25 | bitflags = "2" 26 | bytes = "1" 27 | derive_more = { version = "2", features = ["display", "error", "from"] } 28 | futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } 29 | http-range = "0.1.4" 30 | log = "0.4" 31 | mime = "0.3.9" 32 | mime_guess = "2.0.1" 33 | percent-encoding = "2.1" 34 | pin-project-lite = "0.2.7" 35 | v_htmlescape = "0.15.5" 36 | 37 | # experimental-io-uring 38 | [target.'cfg(target_os = "linux")'.dependencies] 39 | tokio-uring = { version = "0.5", optional = true, features = ["bytes"] } 40 | actix-server = { version = "2.4", optional = true } # ensure matching tokio-uring versions 41 | 42 | [dev-dependencies] 43 | actix-rt = "2.7" 44 | actix-test = "0.1" 45 | actix-web = "4" 46 | env_logger = "0.11" 47 | tempfile = "3.2" 48 | 49 | [lints] 50 | workspace = true 51 | -------------------------------------------------------------------------------- /actix-files/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /actix-files/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /actix-files/README.md: -------------------------------------------------------------------------------- 1 | # `actix-files` 2 | 3 | 4 | 5 | [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) 6 | [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.6)](https://docs.rs/actix-files/0.6.6) 7 | ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) 8 | ![License](https://img.shields.io/crates/l/actix-files.svg) 9 |
10 | [![dependency status](https://deps.rs/crate/actix-files/0.6.6/status.svg)](https://deps.rs/crate/actix-files/0.6.6) 11 | [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) 12 | [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) 13 | 14 | 15 | 16 | 17 | 18 | Static file serving for Actix Web. 19 | 20 | Provides a non-blocking service for serving static files from disk. 21 | 22 | ## Examples 23 | 24 | ```rust 25 | use actix_web::App; 26 | use actix_files::Files; 27 | 28 | let app = App::new() 29 | .service(Files::new("/static", ".").prefer_utf8(true)); 30 | ``` 31 | 32 | 33 | -------------------------------------------------------------------------------- /actix-files/examples/guarded-listing.rs: -------------------------------------------------------------------------------- 1 | use actix_files::Files; 2 | use actix_web::{get, guard, middleware, App, HttpServer, Responder}; 3 | 4 | const EXAMPLES_DIR: &str = concat![env!("CARGO_MANIFEST_DIR"), "/examples"]; 5 | 6 | #[get("/")] 7 | async fn index() -> impl Responder { 8 | "Hello world!" 9 | } 10 | 11 | #[actix_web::main] 12 | async fn main() -> std::io::Result<()> { 13 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 14 | 15 | log::info!("starting HTTP server at http://localhost:8080"); 16 | 17 | HttpServer::new(|| { 18 | App::new() 19 | .service(index) 20 | .service( 21 | Files::new("/assets", EXAMPLES_DIR) 22 | .show_files_listing() 23 | .guard(guard::Header("show-listing", "?1")), 24 | ) 25 | .service(Files::new("/assets", EXAMPLES_DIR)) 26 | .wrap(middleware::Compress::default()) 27 | .wrap(middleware::Logger::default()) 28 | }) 29 | .bind(("127.0.0.1", 8080))? 30 | .workers(2) 31 | .run() 32 | .await 33 | } 34 | -------------------------------------------------------------------------------- /actix-files/src/encoding.rs: -------------------------------------------------------------------------------- 1 | use mime::Mime; 2 | 3 | /// Transforms MIME `text/*` types into their UTF-8 equivalent, if supported. 4 | /// 5 | /// MIME types that are converted 6 | /// - application/javascript 7 | /// - text/html 8 | /// - text/css 9 | /// - text/plain 10 | /// - text/csv 11 | /// - text/tab-separated-values 12 | pub(crate) fn equiv_utf8_text(ct: Mime) -> Mime { 13 | // use (roughly) order of file-type popularity for a web server 14 | 15 | if ct == mime::APPLICATION_JAVASCRIPT { 16 | return mime::APPLICATION_JAVASCRIPT_UTF_8; 17 | } 18 | 19 | if ct == mime::TEXT_HTML { 20 | return mime::TEXT_HTML_UTF_8; 21 | } 22 | 23 | if ct == mime::TEXT_CSS { 24 | return mime::TEXT_CSS_UTF_8; 25 | } 26 | 27 | if ct == mime::TEXT_PLAIN { 28 | return mime::TEXT_PLAIN_UTF_8; 29 | } 30 | 31 | if ct == mime::TEXT_CSV { 32 | return mime::TEXT_CSV_UTF_8; 33 | } 34 | 35 | if ct == mime::TEXT_TAB_SEPARATED_VALUES { 36 | return mime::TEXT_TAB_SEPARATED_VALUES_UTF_8; 37 | } 38 | 39 | ct 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn test_equiv_utf8_text() { 48 | assert_eq!(equiv_utf8_text(mime::TEXT_PLAIN), mime::TEXT_PLAIN_UTF_8); 49 | assert_eq!(equiv_utf8_text(mime::TEXT_XML), mime::TEXT_XML); 50 | assert_eq!(equiv_utf8_text(mime::IMAGE_PNG), mime::IMAGE_PNG); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /actix-files/src/error.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{http::StatusCode, ResponseError}; 2 | use derive_more::Display; 3 | 4 | /// Errors which can occur when serving static files. 5 | #[derive(Debug, PartialEq, Eq, Display)] 6 | pub enum FilesError { 7 | /// Path is not a directory. 8 | #[allow(dead_code)] 9 | #[display("path is not a directory. Unable to serve static files")] 10 | IsNotDirectory, 11 | 12 | /// Cannot render directory. 13 | #[display("unable to render directory without index file")] 14 | IsDirectory, 15 | } 16 | 17 | impl ResponseError for FilesError { 18 | /// Returns `404 Not Found`. 19 | fn status_code(&self) -> StatusCode { 20 | StatusCode::NOT_FOUND 21 | } 22 | } 23 | 24 | #[derive(Debug, PartialEq, Eq, Display)] 25 | #[non_exhaustive] 26 | pub enum UriSegmentError { 27 | /// Segment started with the wrapped invalid character. 28 | #[display("segment started with invalid character: ('{_0}')")] 29 | BadStart(char), 30 | 31 | /// Segment contained the wrapped invalid character. 32 | #[display("segment contained invalid character ('{_0}')")] 33 | BadChar(char), 34 | 35 | /// Segment ended with the wrapped invalid character. 36 | #[display("segment ended with invalid character: ('{_0}')")] 37 | BadEnd(char), 38 | 39 | /// Path is not a valid UTF-8 string after percent-decoding. 40 | #[display("path is not a valid UTF-8 string after percent-decoding")] 41 | NotValidUtf8, 42 | } 43 | 44 | impl ResponseError for UriSegmentError { 45 | /// Returns `400 Bad Request`. 46 | fn status_code(&self) -> StatusCode { 47 | StatusCode::BAD_REQUEST 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /actix-files/tests/encoding.rs: -------------------------------------------------------------------------------- 1 | use actix_files::{Files, NamedFile}; 2 | use actix_web::{ 3 | http::{ 4 | header::{self, HeaderValue}, 5 | StatusCode, 6 | }, 7 | test::{self, TestRequest}, 8 | web, App, 9 | }; 10 | 11 | #[actix_web::test] 12 | async fn test_utf8_file_contents() { 13 | // use default ISO-8859-1 encoding 14 | let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await; 15 | 16 | let req = TestRequest::with_uri("/utf8.txt").to_request(); 17 | let res = test::call_service(&srv, req).await; 18 | 19 | assert_eq!(res.status(), StatusCode::OK); 20 | assert_eq!( 21 | res.headers().get(header::CONTENT_TYPE), 22 | Some(&HeaderValue::from_static("text/plain; charset=utf-8")), 23 | ); 24 | 25 | // disable UTF-8 attribute 26 | let srv = 27 | test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(false))).await; 28 | 29 | let req = TestRequest::with_uri("/utf8.txt").to_request(); 30 | let res = test::call_service(&srv, req).await; 31 | 32 | assert_eq!(res.status(), StatusCode::OK); 33 | assert_eq!( 34 | res.headers().get(header::CONTENT_TYPE), 35 | Some(&HeaderValue::from_static("text/plain")), 36 | ); 37 | } 38 | 39 | #[actix_web::test] 40 | async fn partial_range_response_encoding() { 41 | let srv = test::init_service(App::new().default_service(web::to(|| async { 42 | NamedFile::open_async("./tests/test.binary").await.unwrap() 43 | }))) 44 | .await; 45 | 46 | // range request without accept-encoding returns no content-encoding header 47 | let req = TestRequest::with_uri("/") 48 | .append_header((header::RANGE, "bytes=10-20")) 49 | .to_request(); 50 | let res = test::call_service(&srv, req).await; 51 | assert_eq!(res.status(), StatusCode::PARTIAL_CONTENT); 52 | assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); 53 | 54 | // range request with accept-encoding returns a content-encoding header 55 | let req = TestRequest::with_uri("/") 56 | .append_header((header::RANGE, "bytes=10-20")) 57 | .append_header((header::ACCEPT_ENCODING, "identity")) 58 | .to_request(); 59 | let res = test::call_service(&srv, req).await; 60 | assert_eq!(res.status(), StatusCode::PARTIAL_CONTENT); 61 | assert_eq!( 62 | res.headers().get(header::CONTENT_ENCODING).unwrap(), 63 | "identity" 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /actix-files/tests/fixtures/guards/first/index.txt: -------------------------------------------------------------------------------- 1 | first -------------------------------------------------------------------------------- /actix-files/tests/fixtures/guards/second/index.txt: -------------------------------------------------------------------------------- 1 | second -------------------------------------------------------------------------------- /actix-files/tests/guard.rs: -------------------------------------------------------------------------------- 1 | use actix_files::Files; 2 | use actix_web::{ 3 | guard::Host, 4 | http::StatusCode, 5 | test::{self, TestRequest}, 6 | App, 7 | }; 8 | use bytes::Bytes; 9 | 10 | #[actix_web::test] 11 | async fn test_guard_filter() { 12 | let srv = test::init_service( 13 | App::new() 14 | .service(Files::new("/", "./tests/fixtures/guards/first").guard(Host("first.com"))) 15 | .service(Files::new("/", "./tests/fixtures/guards/second").guard(Host("second.com"))), 16 | ) 17 | .await; 18 | 19 | let req = TestRequest::with_uri("/index.txt") 20 | .append_header(("Host", "first.com")) 21 | .to_request(); 22 | let res = test::call_service(&srv, req).await; 23 | 24 | assert_eq!(res.status(), StatusCode::OK); 25 | assert_eq!(test::read_body(res).await, Bytes::from("first")); 26 | 27 | let req = TestRequest::with_uri("/index.txt") 28 | .append_header(("Host", "second.com")) 29 | .to_request(); 30 | let res = test::call_service(&srv, req).await; 31 | 32 | assert_eq!(res.status(), StatusCode::OK); 33 | assert_eq!(test::read_body(res).await, Bytes::from("second")); 34 | } 35 | -------------------------------------------------------------------------------- /actix-files/tests/symlink-test.png: -------------------------------------------------------------------------------- 1 | test.png -------------------------------------------------------------------------------- /actix-files/tests/test space.binary: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/actix-web/10f56f64123b06e4bc8b476761ecc3765736f480/actix-files/tests/test space.binary -------------------------------------------------------------------------------- /actix-files/tests/test.binary: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/actix-web/10f56f64123b06e4bc8b476761ecc3765736f480/actix-files/tests/test.binary -------------------------------------------------------------------------------- /actix-files/tests/test.js: -------------------------------------------------------------------------------- 1 | // this file is empty. 2 | -------------------------------------------------------------------------------- /actix-files/tests/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/actix-web/10f56f64123b06e4bc8b476761ecc3765736f480/actix-files/tests/test.png -------------------------------------------------------------------------------- /actix-files/tests/traversal.rs: -------------------------------------------------------------------------------- 1 | use actix_files::Files; 2 | use actix_web::{ 3 | http::StatusCode, 4 | test::{self, TestRequest}, 5 | App, 6 | }; 7 | 8 | #[actix_rt::test] 9 | async fn test_directory_traversal_prevention() { 10 | let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await; 11 | 12 | let req = TestRequest::with_uri("/../../../../../../../../../../../etc/passwd").to_request(); 13 | let res = test::call_service(&srv, req).await; 14 | assert_eq!(res.status(), StatusCode::NOT_FOUND); 15 | 16 | let req = TestRequest::with_uri( 17 | "/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd", 18 | ) 19 | .to_request(); 20 | let res = test::call_service(&srv, req).await; 21 | assert_eq!(res.status(), StatusCode::NOT_FOUND); 22 | 23 | let req = TestRequest::with_uri("/%00/etc/passwd%00").to_request(); 24 | let res = test::call_service(&srv, req).await; 25 | assert_eq!(res.status(), StatusCode::NOT_FOUND); 26 | } 27 | -------------------------------------------------------------------------------- /actix-files/tests/utf8.txt: -------------------------------------------------------------------------------- 1 | 中文内容显示正确。 2 | 3 | English is OK. 4 | -------------------------------------------------------------------------------- /actix-http-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix-http-test" 3 | version = "3.2.0" 4 | authors = ["Nikolay Kim "] 5 | description = "Various helpers for Actix applications to use during testing" 6 | keywords = ["http", "web", "framework", "async", "futures"] 7 | homepage = "https://actix.rs" 8 | repository = "https://github.com/actix/actix-web" 9 | categories = [ 10 | "network-programming", 11 | "asynchronous", 12 | "web-programming::http-server", 13 | "web-programming::websocket", 14 | ] 15 | license = "MIT OR Apache-2.0" 16 | edition = "2021" 17 | 18 | [package.metadata.docs.rs] 19 | features = [] 20 | 21 | [package.metadata.cargo_check_external_types] 22 | allowed_external_types = [ 23 | "actix_codec::*", 24 | "actix_http::*", 25 | "actix_server::*", 26 | "awc::*", 27 | "bytes::*", 28 | "futures_core::*", 29 | "http::*", 30 | "tokio::*", 31 | ] 32 | 33 | [features] 34 | default = [] 35 | 36 | # openssl 37 | openssl = ["tls-openssl", "awc/openssl"] 38 | 39 | [dependencies] 40 | actix-codec = "0.5" 41 | actix-rt = "2.2" 42 | actix-server = "2" 43 | actix-service = "2" 44 | actix-tls = "3" 45 | actix-utils = "3" 46 | awc = { version = "3", default-features = false } 47 | 48 | bytes = "1" 49 | futures-core = { version = "0.3.17", default-features = false } 50 | http = "0.2.7" 51 | log = "0.4" 52 | serde = "1" 53 | serde_json = "1" 54 | serde_urlencoded = "0.7" 55 | slab = "0.4" 56 | socket2 = "0.5" 57 | tls-openssl = { version = "0.10.55", package = "openssl", optional = true } 58 | tokio = { version = "1.38.2", features = ["sync"] } 59 | 60 | [dev-dependencies] 61 | actix-http = "3" 62 | 63 | [lints] 64 | workspace = true 65 | -------------------------------------------------------------------------------- /actix-http-test/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /actix-http-test/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /actix-http-test/README.md: -------------------------------------------------------------------------------- 1 | # `actix-http-test` 2 | 3 | 4 | 5 | [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) 6 | [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.2.0)](https://docs.rs/actix-http-test/3.2.0) 7 | ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) 8 | ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) 9 |
10 | [![Dependency Status](https://deps.rs/crate/actix-http-test/3.2.0/status.svg)](https://deps.rs/crate/actix-http-test/3.2.0) 11 | [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) 12 | [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) 13 | 14 | 15 | 16 | 17 | 18 | Various helpers for Actix applications to use during testing. 19 | 20 | 21 | -------------------------------------------------------------------------------- /actix-http/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /actix-http/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /actix-http/README.md: -------------------------------------------------------------------------------- 1 | # `actix-http` 2 | 3 | > HTTP types and services for the Actix ecosystem. 4 | 5 | 6 | 7 | [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) 8 | [![Documentation](https://docs.rs/actix-http/badge.svg?version=3.11.0)](https://docs.rs/actix-http/3.11.0) 9 | ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) 10 | ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg) 11 |
12 | [![dependency status](https://deps.rs/crate/actix-http/3.11.0/status.svg)](https://deps.rs/crate/actix-http/3.11.0) 13 | [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) 14 | [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) 15 | 16 | 17 | 18 | ## Examples 19 | 20 | ```rust 21 | use std::{env, io}; 22 | 23 | use actix_http::{HttpService, Response}; 24 | use actix_server::Server; 25 | use futures_util::future; 26 | use http::header::HeaderValue; 27 | use tracing::info; 28 | 29 | #[actix_rt::main] 30 | async fn main() -> io::Result<()> { 31 | env::set_var("RUST_LOG", "hello_world=info"); 32 | env_logger::init(); 33 | 34 | Server::build() 35 | .bind("hello-world", "127.0.0.1:8080", || { 36 | HttpService::build() 37 | .client_timeout(1000) 38 | .client_disconnect(1000) 39 | .finish(|_req| { 40 | info!("{:?}", _req); 41 | let mut res = Response::Ok(); 42 | res.header("x-head", HeaderValue::from_static("dummy value!")); 43 | future::ok::<_, ()>(res.body("Hello world!")) 44 | }) 45 | .tcp() 46 | })? 47 | .run() 48 | .await 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /actix-http/benches/date-formatting.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | 3 | use actix_http::header::HttpDate; 4 | use divan::{black_box, AllocProfiler, Bencher}; 5 | 6 | #[global_allocator] 7 | static ALLOC: AllocProfiler = AllocProfiler::system(); 8 | 9 | #[divan::bench] 10 | fn date_formatting(b: Bencher<'_, '_>) { 11 | let now = SystemTime::now(); 12 | 13 | b.bench(|| { 14 | black_box(HttpDate::from(black_box(now)).to_string()); 15 | }) 16 | } 17 | 18 | fn main() { 19 | divan::main(); 20 | } 21 | -------------------------------------------------------------------------------- /actix-http/examples/actix-web.rs: -------------------------------------------------------------------------------- 1 | use actix_http::HttpService; 2 | use actix_server::Server; 3 | use actix_service::map_config; 4 | use actix_web::{dev::AppConfig, get, App, Responder}; 5 | 6 | #[get("/")] 7 | async fn index() -> impl Responder { 8 | "Hello, world. From Actix Web!" 9 | } 10 | 11 | #[tokio::main(flavor = "current_thread")] 12 | async fn main() -> std::io::Result<()> { 13 | Server::build() 14 | .bind("hello-world", "127.0.0.1:8080", || { 15 | // construct actix-web app 16 | let app = App::new().service(index); 17 | 18 | HttpService::build() 19 | // pass the app to service builder 20 | // map_config is used to map App's configuration to ServiceBuilder 21 | // h1 will configure server to only use HTTP/1.1 22 | .h1(map_config(app, |_| AppConfig::default())) 23 | .tcp() 24 | })? 25 | .run() 26 | .await 27 | } 28 | -------------------------------------------------------------------------------- /actix-http/examples/bench.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::Infallible, io, time::Duration}; 2 | 3 | use actix_http::{HttpService, Request, Response, StatusCode}; 4 | use actix_server::Server; 5 | use once_cell::sync::Lazy; 6 | 7 | static STR: Lazy = Lazy::new(|| "HELLO WORLD ".repeat(20)); 8 | 9 | #[actix_rt::main] 10 | async fn main() -> io::Result<()> { 11 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 12 | 13 | Server::build() 14 | .bind("dispatcher-benchmark", ("127.0.0.1", 8080), || { 15 | HttpService::build() 16 | .client_request_timeout(Duration::from_secs(1)) 17 | .finish(|_: Request| async move { 18 | let mut res = Response::build(StatusCode::OK); 19 | Ok::<_, Infallible>(res.body(&**STR)) 20 | }) 21 | .tcp() 22 | })? 23 | // limiting number of workers so that bench client is not sharing as many resources 24 | .workers(4) 25 | .run() 26 | .await 27 | } 28 | -------------------------------------------------------------------------------- /actix-http/examples/echo.rs: -------------------------------------------------------------------------------- 1 | use std::{io, time::Duration}; 2 | 3 | use actix_http::{Error, HttpService, Request, Response, StatusCode}; 4 | use actix_server::Server; 5 | use bytes::BytesMut; 6 | use futures_util::StreamExt as _; 7 | use http::header::HeaderValue; 8 | use tracing::info; 9 | 10 | #[actix_rt::main] 11 | async fn main() -> io::Result<()> { 12 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 13 | 14 | Server::build() 15 | .bind("echo", ("127.0.0.1", 8080), || { 16 | HttpService::build() 17 | .client_request_timeout(Duration::from_secs(1)) 18 | .client_disconnect_timeout(Duration::from_secs(1)) 19 | // handles HTTP/1.1 and HTTP/2 20 | .finish(|mut req: Request| async move { 21 | let mut body = BytesMut::new(); 22 | while let Some(item) = req.payload().next().await { 23 | body.extend_from_slice(&item?); 24 | } 25 | 26 | info!("request body: {body:?}"); 27 | 28 | let res = Response::build(StatusCode::OK) 29 | .insert_header(("x-head", HeaderValue::from_static("dummy value!"))) 30 | .body(body); 31 | 32 | Ok::<_, Error>(res) 33 | }) 34 | .tcp() // No TLS 35 | })? 36 | .run() 37 | .await 38 | } 39 | -------------------------------------------------------------------------------- /actix-http/examples/echo2.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use actix_http::{ 4 | body::{BodyStream, MessageBody}, 5 | header, Error, HttpMessage, HttpService, Request, Response, StatusCode, 6 | }; 7 | 8 | async fn handle_request(mut req: Request) -> Result, Error> { 9 | let mut res = Response::build(StatusCode::OK); 10 | 11 | if let Some(ct) = req.headers().get(header::CONTENT_TYPE) { 12 | res.insert_header((header::CONTENT_TYPE, ct)); 13 | } 14 | 15 | // echo request payload stream as (chunked) response body 16 | let res = res.message_body(BodyStream::new(req.payload().take()))?; 17 | 18 | Ok(res) 19 | } 20 | 21 | #[actix_rt::main] 22 | async fn main() -> io::Result<()> { 23 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 24 | 25 | actix_server::Server::build() 26 | .bind("echo", ("127.0.0.1", 8080), || { 27 | HttpService::build() 28 | // handles HTTP/1.1 only 29 | .h1(handle_request) 30 | // No TLS 31 | .tcp() 32 | })? 33 | .run() 34 | .await 35 | } 36 | -------------------------------------------------------------------------------- /actix-http/examples/h2c-detect.rs: -------------------------------------------------------------------------------- 1 | //! An example that supports automatic selection of plaintext h1/h2c connections. 2 | //! 3 | //! Notably, both the following commands will work. 4 | //! ```console 5 | //! $ curl --http1.1 'http://localhost:8080/' 6 | //! $ curl --http2-prior-knowledge 'http://localhost:8080/' 7 | //! ``` 8 | 9 | use std::{convert::Infallible, io}; 10 | 11 | use actix_http::{body::BodyStream, HttpService, Request, Response, StatusCode}; 12 | use actix_server::Server; 13 | 14 | #[tokio::main(flavor = "current_thread")] 15 | async fn main() -> io::Result<()> { 16 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 17 | 18 | Server::build() 19 | .bind("h2c-detect", ("127.0.0.1", 8080), || { 20 | HttpService::build() 21 | .finish(|_req: Request| async move { 22 | Ok::<_, Infallible>(Response::build(StatusCode::OK).body(BodyStream::new( 23 | futures_util::stream::iter([ 24 | Ok::<_, String>("123".into()), 25 | Err("wertyuikmnbvcxdfty6t".to_owned()), 26 | ]), 27 | ))) 28 | }) 29 | .tcp_auto_h2c() 30 | })? 31 | .workers(2) 32 | .run() 33 | .await 34 | } 35 | -------------------------------------------------------------------------------- /actix-http/examples/h2spec.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::Infallible, io}; 2 | 3 | use actix_http::{HttpService, Request, Response, StatusCode}; 4 | use actix_server::Server; 5 | use once_cell::sync::Lazy; 6 | 7 | static STR: Lazy = Lazy::new(|| "HELLO WORLD ".repeat(100)); 8 | 9 | #[actix_rt::main] 10 | async fn main() -> io::Result<()> { 11 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 12 | 13 | Server::build() 14 | .bind("h2spec", ("127.0.0.1", 8080), || { 15 | HttpService::build() 16 | .h2(|_: Request| async move { 17 | let mut res = Response::build(StatusCode::OK); 18 | Ok::<_, Infallible>(res.body(&**STR)) 19 | }) 20 | .tcp() 21 | })? 22 | .workers(4) 23 | .run() 24 | .await 25 | } 26 | -------------------------------------------------------------------------------- /actix-http/examples/hello-world.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::Infallible, io, time::Duration}; 2 | 3 | use actix_http::{header::HeaderValue, HttpService, Request, Response, StatusCode}; 4 | use actix_server::Server; 5 | use tracing::info; 6 | 7 | #[actix_rt::main] 8 | async fn main() -> io::Result<()> { 9 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 10 | 11 | Server::build() 12 | .bind("hello-world", ("127.0.0.1", 8080), || { 13 | HttpService::build() 14 | .client_request_timeout(Duration::from_secs(1)) 15 | .client_disconnect_timeout(Duration::from_secs(1)) 16 | .on_connect_ext(|_, ext| { 17 | ext.insert(42u32); 18 | }) 19 | .finish(|req: Request| async move { 20 | info!("{req:?}"); 21 | 22 | let mut res = Response::build(StatusCode::OK); 23 | res.insert_header(("x-head", HeaderValue::from_static("dummy value!"))); 24 | 25 | let forty_two = req.conn_data::().unwrap().to_string(); 26 | res.insert_header(("x-forty-two", HeaderValue::from_str(&forty_two).unwrap())); 27 | 28 | Ok::<_, Infallible>(res.body("Hello world!")) 29 | }) 30 | .tcp() 31 | })? 32 | .run() 33 | .await 34 | } 35 | -------------------------------------------------------------------------------- /actix-http/examples/streaming-error.rs: -------------------------------------------------------------------------------- 1 | //! Example showing response body (chunked) stream erroring. 2 | //! 3 | //! Test using `nc` or `curl`. 4 | //! ```sh 5 | //! $ curl -vN 127.0.0.1:8080 6 | //! $ echo 'GET / HTTP/1.1\n\n' | nc 127.0.0.1 8080 7 | //! ``` 8 | 9 | use std::{convert::Infallible, io, time::Duration}; 10 | 11 | use actix_http::{body::BodyStream, HttpService, Response}; 12 | use actix_server::Server; 13 | use async_stream::stream; 14 | use bytes::Bytes; 15 | use tracing::info; 16 | 17 | #[actix_rt::main] 18 | async fn main() -> io::Result<()> { 19 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 20 | 21 | Server::build() 22 | .bind("streaming-error", ("127.0.0.1", 8080), || { 23 | HttpService::build() 24 | .finish(|req| async move { 25 | info!("{req:?}"); 26 | let res = Response::ok(); 27 | 28 | Ok::<_, Infallible>(res.set_body(BodyStream::new(stream! { 29 | yield Ok(Bytes::from("123")); 30 | yield Ok(Bytes::from("456")); 31 | 32 | actix_rt::time::sleep(Duration::from_secs(1)).await; 33 | 34 | yield Err(io::Error::other("abc")); 35 | }))) 36 | }) 37 | .tcp() 38 | })? 39 | .run() 40 | .await 41 | } 42 | -------------------------------------------------------------------------------- /actix-http/examples/tls_rustls.rs: -------------------------------------------------------------------------------- 1 | //! Demonstrates TLS configuration (via Rustls) for HTTP/1.1 and HTTP/2 connections. 2 | //! 3 | //! Test using cURL: 4 | //! 5 | //! ```console 6 | //! $ curl --insecure https://127.0.0.1:8443 7 | //! Hello World! 8 | //! Protocol: HTTP/2.0 9 | //! 10 | //! $ curl --insecure --http1.1 https://127.0.0.1:8443 11 | //! Hello World! 12 | //! Protocol: HTTP/1.1 13 | //! ``` 14 | 15 | extern crate tls_rustls_023 as rustls; 16 | 17 | use std::io; 18 | 19 | use actix_http::{Error, HttpService, Request, Response}; 20 | use actix_utils::future::ok; 21 | 22 | #[actix_rt::main] 23 | async fn main() -> io::Result<()> { 24 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 25 | 26 | tracing::info!("starting HTTP server at https://127.0.0.1:8443"); 27 | 28 | actix_server::Server::build() 29 | .bind("echo", ("127.0.0.1", 8443), || { 30 | HttpService::build() 31 | .finish(|req: Request| { 32 | let body = format!( 33 | "Hello World!\n\ 34 | Protocol: {:?}", 35 | req.head().version 36 | ); 37 | ok::<_, Error>(Response::ok().set_body(body)) 38 | }) 39 | .rustls_0_23(rustls_config()) 40 | })? 41 | .run() 42 | .await 43 | } 44 | 45 | fn rustls_config() -> rustls::ServerConfig { 46 | let rcgen::CertifiedKey { cert, key_pair } = 47 | rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap(); 48 | let cert_file = cert.pem(); 49 | let key_file = key_pair.serialize_pem(); 50 | 51 | let cert_file = &mut io::BufReader::new(cert_file.as_bytes()); 52 | let key_file = &mut io::BufReader::new(key_file.as_bytes()); 53 | 54 | let cert_chain = rustls_pemfile::certs(cert_file) 55 | .collect::, _>>() 56 | .unwrap(); 57 | let mut keys = rustls_pemfile::pkcs8_private_keys(key_file) 58 | .collect::, _>>() 59 | .unwrap(); 60 | 61 | let mut config = rustls::ServerConfig::builder() 62 | .with_no_client_auth() 63 | .with_single_cert( 64 | cert_chain, 65 | rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)), 66 | ) 67 | .unwrap(); 68 | 69 | const H1_ALPN: &[u8] = b"http/1.1"; 70 | const H2_ALPN: &[u8] = b"h2"; 71 | 72 | config.alpn_protocols.push(H2_ALPN.to_vec()); 73 | config.alpn_protocols.push(H1_ALPN.to_vec()); 74 | 75 | config 76 | } 77 | -------------------------------------------------------------------------------- /actix-http/src/body/mod.rs: -------------------------------------------------------------------------------- 1 | //! Traits and structures to aid consuming and writing HTTP payloads. 2 | //! 3 | //! "Body" and "payload" are used somewhat interchangeably in this documentation. 4 | 5 | // Though the spec kinda reads like "payload" is the possibly-transfer-encoded part of the message 6 | // and the "body" is the intended possibly-decoded version of that. 7 | 8 | mod body_stream; 9 | mod boxed; 10 | mod either; 11 | mod message_body; 12 | mod none; 13 | mod size; 14 | mod sized_stream; 15 | mod utils; 16 | 17 | pub(crate) use self::message_body::MessageBodyMapErr; 18 | pub use self::{ 19 | body_stream::BodyStream, 20 | boxed::BoxBody, 21 | either::EitherBody, 22 | message_body::MessageBody, 23 | none::None, 24 | size::BodySize, 25 | sized_stream::SizedStream, 26 | utils::{to_bytes, to_bytes_limited, BodyLimitExceeded}, 27 | }; 28 | -------------------------------------------------------------------------------- /actix-http/src/body/none.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | convert::Infallible, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use bytes::Bytes; 8 | 9 | use super::{BodySize, MessageBody}; 10 | 11 | /// Body type for responses that forbid payloads. 12 | /// 13 | /// This is distinct from an "empty" response which _would_ contain a `Content-Length` header. 14 | /// For an "empty" body, use `()` or `Bytes::new()`. 15 | /// 16 | /// For example, the HTTP spec forbids a payload to be sent with a `204 No Content` response. 17 | /// In this case, the payload (or lack thereof) is implicit from the status code, so a 18 | /// `Content-Length` header is not required. 19 | #[derive(Debug, Clone, Copy, Default)] 20 | #[non_exhaustive] 21 | pub struct None; 22 | 23 | impl None { 24 | /// Constructs new "none" body. 25 | #[inline] 26 | pub fn new() -> Self { 27 | None 28 | } 29 | } 30 | 31 | impl MessageBody for None { 32 | type Error = Infallible; 33 | 34 | #[inline] 35 | fn size(&self) -> BodySize { 36 | BodySize::None 37 | } 38 | 39 | #[inline] 40 | fn poll_next( 41 | self: Pin<&mut Self>, 42 | _cx: &mut Context<'_>, 43 | ) -> Poll>> { 44 | Poll::Ready(Option::None) 45 | } 46 | 47 | #[inline] 48 | fn try_into_bytes(self) -> Result { 49 | Ok(Bytes::new()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /actix-http/src/body/size.rs: -------------------------------------------------------------------------------- 1 | /// Body size hint. 2 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 3 | pub enum BodySize { 4 | /// Implicitly empty body. 5 | /// 6 | /// Will omit the Content-Length header. Used for responses to certain methods (e.g., `HEAD`) or 7 | /// with particular status codes (e.g., 204 No Content). Consumers that read this as a body size 8 | /// hint are allowed to make optimizations that skip reading or writing the payload. 9 | None, 10 | 11 | /// Known size body. 12 | /// 13 | /// Will write `Content-Length: N` header. 14 | Sized(u64), 15 | 16 | /// Unknown size body. 17 | /// 18 | /// Will not write Content-Length header. Can be used with chunked Transfer-Encoding. 19 | Stream, 20 | } 21 | 22 | impl BodySize { 23 | /// Equivalent to `BodySize::Sized(0)`; 24 | pub const ZERO: Self = Self::Sized(0); 25 | 26 | /// Returns true if size hint indicates omitted or empty body. 27 | /// 28 | /// Streams will return false because it cannot be known without reading the stream. 29 | /// 30 | /// ``` 31 | /// # use actix_http::body::BodySize; 32 | /// assert!(BodySize::None.is_eof()); 33 | /// assert!(BodySize::Sized(0).is_eof()); 34 | /// 35 | /// assert!(!BodySize::Sized(64).is_eof()); 36 | /// assert!(!BodySize::Stream.is_eof()); 37 | /// ``` 38 | pub fn is_eof(&self) -> bool { 39 | matches!(self, BodySize::None | BodySize::Sized(0)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /actix-http/src/encoding/mod.rs: -------------------------------------------------------------------------------- 1 | //! Content-Encoding support. 2 | 3 | use std::io; 4 | 5 | use bytes::{Bytes, BytesMut}; 6 | 7 | mod decoder; 8 | mod encoder; 9 | 10 | pub use self::{decoder::Decoder, encoder::Encoder}; 11 | 12 | /// Special-purpose writer for streaming (de-)compression. 13 | /// 14 | /// Pre-allocates 8KiB of capacity. 15 | struct Writer { 16 | buf: BytesMut, 17 | } 18 | 19 | impl Writer { 20 | fn new() -> Writer { 21 | Writer { 22 | buf: BytesMut::with_capacity(8192), 23 | } 24 | } 25 | 26 | fn take(&mut self) -> Bytes { 27 | self.buf.split().freeze() 28 | } 29 | } 30 | 31 | impl io::Write for Writer { 32 | fn write(&mut self, buf: &[u8]) -> io::Result { 33 | self.buf.extend_from_slice(buf); 34 | Ok(buf.len()) 35 | } 36 | 37 | fn flush(&mut self) -> io::Result<()> { 38 | Ok(()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /actix-http/src/h1/expect.rs: -------------------------------------------------------------------------------- 1 | use actix_service::{Service, ServiceFactory}; 2 | use actix_utils::future::{ready, Ready}; 3 | 4 | use crate::{Error, Request}; 5 | 6 | pub struct ExpectHandler; 7 | 8 | impl ServiceFactory for ExpectHandler { 9 | type Response = Request; 10 | type Error = Error; 11 | type Config = (); 12 | type Service = ExpectHandler; 13 | type InitError = Error; 14 | type Future = Ready>; 15 | 16 | fn new_service(&self, _: Self::Config) -> Self::Future { 17 | ready(Ok(ExpectHandler)) 18 | } 19 | } 20 | 21 | impl Service for ExpectHandler { 22 | type Response = Request; 23 | type Error = Error; 24 | type Future = Ready>; 25 | 26 | actix_service::always_ready!(); 27 | 28 | fn call(&self, req: Request) -> Self::Future { 29 | ready(Ok(req)) 30 | // TODO: add some way to trigger error 31 | // Err(error::ErrorExpectationFailed("test")) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /actix-http/src/h1/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP/1 protocol implementation. 2 | 3 | use bytes::{Bytes, BytesMut}; 4 | 5 | mod chunked; 6 | mod client; 7 | mod codec; 8 | mod decoder; 9 | mod dispatcher; 10 | #[cfg(test)] 11 | mod dispatcher_tests; 12 | mod encoder; 13 | mod expect; 14 | mod payload; 15 | mod service; 16 | mod timer; 17 | mod upgrade; 18 | mod utils; 19 | 20 | pub use self::{ 21 | client::{ClientCodec, ClientPayloadCodec}, 22 | codec::Codec, 23 | dispatcher::Dispatcher, 24 | expect::ExpectHandler, 25 | payload::Payload, 26 | service::{H1Service, H1ServiceHandler}, 27 | upgrade::UpgradeHandler, 28 | utils::SendResponse, 29 | }; 30 | 31 | #[derive(Debug)] 32 | /// Codec message 33 | pub enum Message { 34 | /// HTTP message. 35 | Item(T), 36 | 37 | /// Payload chunk. 38 | Chunk(Option), 39 | } 40 | 41 | impl From for Message { 42 | fn from(item: T) -> Self { 43 | Message::Item(item) 44 | } 45 | } 46 | 47 | /// Incoming request type 48 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 49 | pub enum MessageType { 50 | None, 51 | Payload, 52 | Stream, 53 | } 54 | 55 | const LW: usize = 2 * 1024; 56 | const HW: usize = 32 * 1024; 57 | 58 | pub(crate) fn reserve_readbuf(src: &mut BytesMut) { 59 | let cap = src.capacity(); 60 | if cap < LW { 61 | src.reserve(HW - cap); 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::*; 68 | use crate::Request; 69 | 70 | impl Message { 71 | pub fn message(self) -> Request { 72 | match self { 73 | Message::Item(req) => req, 74 | _ => panic!("error"), 75 | } 76 | } 77 | 78 | pub fn chunk(self) -> Bytes { 79 | match self { 80 | Message::Chunk(Some(data)) => data, 81 | _ => panic!("error"), 82 | } 83 | } 84 | 85 | pub fn eof(self) -> bool { 86 | match self { 87 | Message::Chunk(None) => true, 88 | Message::Chunk(Some(_)) => false, 89 | _ => panic!("error"), 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /actix-http/src/h1/timer.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, future::Future, pin::Pin, task::Context}; 2 | 3 | use actix_rt::time::{Instant, Sleep}; 4 | use tracing::trace; 5 | 6 | #[derive(Debug)] 7 | pub(super) enum TimerState { 8 | Disabled, 9 | Inactive, 10 | Active { timer: Pin> }, 11 | } 12 | 13 | impl TimerState { 14 | pub(super) fn new(enabled: bool) -> Self { 15 | if enabled { 16 | Self::Inactive 17 | } else { 18 | Self::Disabled 19 | } 20 | } 21 | 22 | pub(super) fn is_enabled(&self) -> bool { 23 | matches!(self, Self::Active { .. } | Self::Inactive) 24 | } 25 | 26 | pub(super) fn set(&mut self, timer: Sleep, line: u32) { 27 | if matches!(self, Self::Disabled) { 28 | trace!("setting disabled timer from line {}", line); 29 | } 30 | 31 | *self = Self::Active { 32 | timer: Box::pin(timer), 33 | }; 34 | } 35 | 36 | pub(super) fn set_and_init(&mut self, cx: &mut Context<'_>, timer: Sleep, line: u32) { 37 | self.set(timer, line); 38 | self.init(cx); 39 | } 40 | 41 | pub(super) fn clear(&mut self, line: u32) { 42 | if matches!(self, Self::Disabled) { 43 | trace!("trying to clear a disabled timer from line {}", line); 44 | } 45 | 46 | if matches!(self, Self::Inactive) { 47 | trace!("trying to clear an inactive timer from line {}", line); 48 | } 49 | 50 | *self = Self::Inactive; 51 | } 52 | 53 | pub(super) fn init(&mut self, cx: &mut Context<'_>) { 54 | if let TimerState::Active { timer } = self { 55 | let _ = timer.as_mut().poll(cx); 56 | } 57 | } 58 | } 59 | 60 | impl fmt::Display for TimerState { 61 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 62 | match self { 63 | TimerState::Disabled => f.write_str("timer is disabled"), 64 | TimerState::Inactive => f.write_str("timer is inactive"), 65 | TimerState::Active { timer } => { 66 | let deadline = timer.deadline(); 67 | let now = Instant::now(); 68 | 69 | if deadline < now { 70 | f.write_str("timer is active and has reached deadline") 71 | } else { 72 | write!( 73 | f, 74 | "timer is active and due to expire in {} milliseconds", 75 | ((deadline - now).as_secs_f32() * 1000.0) 76 | ) 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /actix-http/src/h1/upgrade.rs: -------------------------------------------------------------------------------- 1 | use actix_codec::Framed; 2 | use actix_service::{Service, ServiceFactory}; 3 | use futures_core::future::LocalBoxFuture; 4 | 5 | use crate::{h1::Codec, Error, Request}; 6 | 7 | pub struct UpgradeHandler; 8 | 9 | impl ServiceFactory<(Request, Framed)> for UpgradeHandler { 10 | type Response = (); 11 | type Error = Error; 12 | type Config = (); 13 | type Service = UpgradeHandler; 14 | type InitError = Error; 15 | type Future = LocalBoxFuture<'static, Result>; 16 | 17 | fn new_service(&self, _: ()) -> Self::Future { 18 | unimplemented!() 19 | } 20 | } 21 | 22 | impl Service<(Request, Framed)> for UpgradeHandler { 23 | type Response = (); 24 | type Error = Error; 25 | type Future = LocalBoxFuture<'static, Result>; 26 | 27 | actix_service::always_ready!(); 28 | 29 | fn call(&self, _: (Request, Framed)) -> Self::Future { 30 | unimplemented!() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /actix-http/src/header/as_name.rs: -------------------------------------------------------------------------------- 1 | //! Sealed [`AsHeaderName`] trait and implementations. 2 | 3 | use std::{borrow::Cow, str::FromStr as _}; 4 | 5 | use http::header::{HeaderName, InvalidHeaderName}; 6 | 7 | /// Sealed trait implemented for types that can be effectively borrowed as a [`HeaderValue`]. 8 | /// 9 | /// [`HeaderValue`]: super::HeaderValue 10 | pub trait AsHeaderName: Sealed {} 11 | 12 | pub struct Seal; 13 | 14 | pub trait Sealed { 15 | fn try_as_name(&self, seal: Seal) -> Result, InvalidHeaderName>; 16 | } 17 | 18 | impl Sealed for HeaderName { 19 | #[inline] 20 | fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { 21 | Ok(Cow::Borrowed(self)) 22 | } 23 | } 24 | impl AsHeaderName for HeaderName {} 25 | 26 | impl Sealed for &HeaderName { 27 | #[inline] 28 | fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { 29 | Ok(Cow::Borrowed(*self)) 30 | } 31 | } 32 | impl AsHeaderName for &HeaderName {} 33 | 34 | impl Sealed for &str { 35 | #[inline] 36 | fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { 37 | HeaderName::from_str(self).map(Cow::Owned) 38 | } 39 | } 40 | impl AsHeaderName for &str {} 41 | 42 | impl Sealed for String { 43 | #[inline] 44 | fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { 45 | HeaderName::from_str(self).map(Cow::Owned) 46 | } 47 | } 48 | impl AsHeaderName for String {} 49 | 50 | impl Sealed for &String { 51 | #[inline] 52 | fn try_as_name(&self, _: Seal) -> Result, InvalidHeaderName> { 53 | HeaderName::from_str(self).map(Cow::Owned) 54 | } 55 | } 56 | impl AsHeaderName for &String {} 57 | -------------------------------------------------------------------------------- /actix-http/src/header/shared/http_date.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, io::Write, str::FromStr, time::SystemTime}; 2 | 3 | use bytes::BytesMut; 4 | use http::header::{HeaderValue, InvalidHeaderValue}; 5 | 6 | use crate::{ 7 | date::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue, helpers::MutWriter, 8 | }; 9 | 10 | /// A timestamp with HTTP-style formatting and parsing. 11 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 12 | pub struct HttpDate(SystemTime); 13 | 14 | impl FromStr for HttpDate { 15 | type Err = ParseError; 16 | 17 | fn from_str(s: &str) -> Result { 18 | match httpdate::parse_http_date(s) { 19 | Ok(sys_time) => Ok(HttpDate(sys_time)), 20 | Err(_) => Err(ParseError::Header), 21 | } 22 | } 23 | } 24 | 25 | impl fmt::Display for HttpDate { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | httpdate::HttpDate::from(self.0).fmt(f) 28 | } 29 | } 30 | 31 | impl TryIntoHeaderValue for HttpDate { 32 | type Error = InvalidHeaderValue; 33 | 34 | fn try_into_value(self) -> Result { 35 | let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH); 36 | let mut wrt = MutWriter(&mut buf); 37 | 38 | // unwrap: date output is known to be well formed and of known length 39 | write!(wrt, "{}", self).unwrap(); 40 | 41 | HeaderValue::from_maybe_shared(buf.split().freeze()) 42 | } 43 | } 44 | 45 | impl From for HttpDate { 46 | fn from(sys_time: SystemTime) -> HttpDate { 47 | HttpDate(sys_time) 48 | } 49 | } 50 | 51 | impl From for SystemTime { 52 | fn from(HttpDate(sys_time): HttpDate) -> SystemTime { 53 | sys_time 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use std::time::Duration; 60 | 61 | use super::*; 62 | 63 | #[test] 64 | fn date_header() { 65 | macro_rules! assert_parsed_date { 66 | ($case:expr, $exp:expr) => { 67 | assert_eq!($case.parse::().unwrap(), $exp); 68 | }; 69 | } 70 | 71 | // 784198117 = SystemTime::from(datetime!(1994-11-07 08:48:37).assume_utc()).duration_since(SystemTime::UNIX_EPOCH)); 72 | let nov_07 = HttpDate(SystemTime::UNIX_EPOCH + Duration::from_secs(784198117)); 73 | 74 | assert_parsed_date!("Mon, 07 Nov 1994 08:48:37 GMT", nov_07); 75 | assert_parsed_date!("Monday, 07-Nov-94 08:48:37 GMT", nov_07); 76 | assert_parsed_date!("Mon Nov 7 08:48:37 1994", nov_07); 77 | 78 | assert!("this-is-no-date".parse::().is_err()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /actix-http/src/header/shared/mod.rs: -------------------------------------------------------------------------------- 1 | //! Originally taken from `hyper::header::shared`. 2 | 3 | pub use language_tags::LanguageTag; 4 | 5 | mod charset; 6 | mod content_encoding; 7 | mod extended; 8 | mod http_date; 9 | mod quality; 10 | mod quality_item; 11 | 12 | pub use self::{ 13 | charset::Charset, 14 | content_encoding::ContentEncoding, 15 | extended::{parse_extended_value, ExtendedValue}, 16 | http_date::HttpDate, 17 | quality::{q, Quality}, 18 | quality_item::QualityItem, 19 | }; 20 | -------------------------------------------------------------------------------- /actix-http/src/keep_alive.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | /// Connection keep-alive config. 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 5 | pub enum KeepAlive { 6 | /// Keep-alive duration. 7 | /// 8 | /// `KeepAlive::Timeout(Duration::ZERO)` is mapped to `KeepAlive::Disabled`. 9 | Timeout(Duration), 10 | 11 | /// Rely on OS to shutdown TCP connection. 12 | /// 13 | /// Some defaults can be very long, check your OS documentation. 14 | Os, 15 | 16 | /// Keep-alive is disabled. 17 | /// 18 | /// Connections will be closed immediately. 19 | Disabled, 20 | } 21 | 22 | impl KeepAlive { 23 | pub(crate) fn enabled(&self) -> bool { 24 | !matches!(self, Self::Disabled) 25 | } 26 | 27 | #[allow(unused)] // used with `http2` feature flag 28 | pub(crate) fn duration(&self) -> Option { 29 | match self { 30 | KeepAlive::Timeout(dur) => Some(*dur), 31 | _ => None, 32 | } 33 | } 34 | 35 | /// Map zero duration to disabled. 36 | pub(crate) fn normalize(self) -> KeepAlive { 37 | match self { 38 | KeepAlive::Timeout(Duration::ZERO) => KeepAlive::Disabled, 39 | ka => ka, 40 | } 41 | } 42 | } 43 | 44 | impl Default for KeepAlive { 45 | fn default() -> Self { 46 | Self::Timeout(Duration::from_secs(5)) 47 | } 48 | } 49 | 50 | impl From for KeepAlive { 51 | fn from(dur: Duration) -> Self { 52 | KeepAlive::Timeout(dur).normalize() 53 | } 54 | } 55 | 56 | impl From> for KeepAlive { 57 | fn from(ka_dur: Option) -> Self { 58 | match ka_dur { 59 | Some(dur) => KeepAlive::from(dur), 60 | None => KeepAlive::Disabled, 61 | } 62 | .normalize() 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | 70 | #[test] 71 | fn from_impls() { 72 | let test: KeepAlive = Duration::from_secs(1).into(); 73 | assert_eq!(test, KeepAlive::Timeout(Duration::from_secs(1))); 74 | 75 | let test: KeepAlive = Duration::from_secs(0).into(); 76 | assert_eq!(test, KeepAlive::Disabled); 77 | 78 | let test: KeepAlive = Some(Duration::from_secs(0)).into(); 79 | assert_eq!(test, KeepAlive::Disabled); 80 | 81 | let test: KeepAlive = None.into(); 82 | assert_eq!(test, KeepAlive::Disabled); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /actix-http/src/notify_on_drop.rs: -------------------------------------------------------------------------------- 1 | /// Test Module for checking the drop state of certain async tasks that are spawned 2 | /// with `actix_rt::spawn` 3 | /// 4 | /// The target task must explicitly generate `NotifyOnDrop` when spawn the task 5 | use std::cell::RefCell; 6 | 7 | thread_local! { 8 | static NOTIFY_DROPPED: RefCell> = const { RefCell::new(None) }; 9 | } 10 | 11 | /// Check if the spawned task is dropped. 12 | /// 13 | /// # Panics 14 | /// Panics when there was no `NotifyOnDrop` instance on current thread. 15 | pub(crate) fn is_dropped() -> bool { 16 | NOTIFY_DROPPED.with(|bool| { 17 | bool.borrow() 18 | .expect("No NotifyOnDrop existed on current thread") 19 | }) 20 | } 21 | 22 | pub(crate) struct NotifyOnDrop; 23 | 24 | impl NotifyOnDrop { 25 | /// # Panics 26 | /// Panics hen construct multiple instances on any given thread. 27 | pub(crate) fn new() -> Self { 28 | NOTIFY_DROPPED.with(|bool| { 29 | let mut bool = bool.borrow_mut(); 30 | if bool.is_some() { 31 | panic!("NotifyOnDrop existed on current thread"); 32 | } else { 33 | *bool = Some(false); 34 | } 35 | }); 36 | 37 | NotifyOnDrop 38 | } 39 | } 40 | 41 | impl Drop for NotifyOnDrop { 42 | fn drop(&mut self) { 43 | NOTIFY_DROPPED.with(|bool| { 44 | if let Some(b) = bool.borrow_mut().as_mut() { 45 | *b = true; 46 | } 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /actix-http/src/requests/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP requests. 2 | 3 | mod head; 4 | mod request; 5 | 6 | pub use self::{ 7 | head::{RequestHead, RequestHeadType}, 8 | request::Request, 9 | }; 10 | -------------------------------------------------------------------------------- /actix-http/src/responses/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP response. 2 | 3 | mod builder; 4 | mod head; 5 | #[allow(clippy::module_inception)] 6 | mod response; 7 | 8 | pub(crate) use self::head::BoxedResponseHead; 9 | pub use self::{builder::ResponseBuilder, head::ResponseHead, response::Response}; 10 | -------------------------------------------------------------------------------- /actix-http/src/ws/mask.rs: -------------------------------------------------------------------------------- 1 | //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) 2 | 3 | /// Mask/unmask a frame. 4 | #[inline] 5 | pub fn apply_mask(buf: &mut [u8], mask: [u8; 4]) { 6 | apply_mask_fast32(buf, mask) 7 | } 8 | 9 | /// A safe unoptimized mask application. 10 | #[inline] 11 | fn apply_mask_fallback(buf: &mut [u8], mask: [u8; 4]) { 12 | for (i, byte) in buf.iter_mut().enumerate() { 13 | *byte ^= mask[i & 3]; 14 | } 15 | } 16 | 17 | /// Faster version of `apply_mask()` which operates on 4-byte blocks. 18 | #[inline] 19 | pub fn apply_mask_fast32(buf: &mut [u8], mask: [u8; 4]) { 20 | let mask_u32 = u32::from_ne_bytes(mask); 21 | 22 | // SAFETY: 23 | // 24 | // buf is a valid slice borrowed mutably from bytes::BytesMut. 25 | // 26 | // un aligned prefix and suffix would be mask/unmask per byte. 27 | // proper aligned middle slice goes into fast path and operates on 4-byte blocks. 28 | let (prefix, words, suffix) = unsafe { buf.align_to_mut::() }; 29 | apply_mask_fallback(prefix, mask); 30 | let head = prefix.len() & 3; 31 | let mask_u32 = if head > 0 { 32 | if cfg!(target_endian = "big") { 33 | mask_u32.rotate_left(8 * head as u32) 34 | } else { 35 | mask_u32.rotate_right(8 * head as u32) 36 | } 37 | } else { 38 | mask_u32 39 | }; 40 | for word in words.iter_mut() { 41 | *word ^= mask_u32; 42 | } 43 | apply_mask_fallback(suffix, mask_u32.to_ne_bytes()); 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use super::*; 49 | 50 | #[test] 51 | fn test_apply_mask() { 52 | let mask = [0x6d, 0xb6, 0xb2, 0x80]; 53 | let unmasked = [ 54 | 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9, 55 | 0x12, 0x03, 56 | ]; 57 | 58 | for data_len in 0..=unmasked.len() { 59 | let unmasked = &unmasked[0..data_len]; 60 | // Check masking with different alignment. 61 | for off in 0..=3 { 62 | if unmasked.len() < off { 63 | continue; 64 | } 65 | let mut masked = unmasked.to_vec(); 66 | apply_mask_fallback(&mut masked[off..], mask); 67 | 68 | let mut masked_fast = unmasked.to_vec(); 69 | apply_mask_fast32(&mut masked_fast[off..], mask); 70 | 71 | assert_eq!(masked, masked_fast); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /actix-http/tests/test.binary: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/actix-web/10f56f64123b06e4bc8b476761ecc3765736f480/actix-http/tests/test.binary -------------------------------------------------------------------------------- /actix-http/tests/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/actix-web/10f56f64123b06e4bc8b476761ecc3765736f480/actix-http/tests/test.png -------------------------------------------------------------------------------- /actix-multipart-derive/CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## Unreleased 4 | 5 | ## 0.7.0 6 | 7 | - Minimum supported Rust version (MSRV) is now 1.72. 8 | 9 | ## 0.6.1 10 | 11 | - Update `syn` dependency to `2`. 12 | - Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency. 13 | 14 | ## 0.6.0 15 | 16 | - Add `MultipartForm` derive macro. 17 | -------------------------------------------------------------------------------- /actix-multipart-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix-multipart-derive" 3 | version = "0.7.0" 4 | authors = ["Jacob Halsey "] 5 | description = "Multipart form derive macro for Actix Web" 6 | keywords = ["http", "web", "framework", "async", "futures"] 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | edition.workspace = true 11 | rust-version.workspace = true 12 | 13 | [package.metadata.docs.rs] 14 | rustdoc-args = ["--cfg", "docsrs"] 15 | all-features = true 16 | 17 | [lib] 18 | proc-macro = true 19 | 20 | [dependencies] 21 | bytesize = "2" 22 | darling = "0.20" 23 | proc-macro2 = "1" 24 | quote = "1" 25 | syn = "2" 26 | 27 | [dev-dependencies] 28 | actix-multipart = "0.7" 29 | actix-web = "4" 30 | rustversion-msrv = "0.100" 31 | trybuild = "1" 32 | 33 | [lints] 34 | workspace = true 35 | -------------------------------------------------------------------------------- /actix-multipart-derive/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /actix-multipart-derive/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /actix-multipart-derive/README.md: -------------------------------------------------------------------------------- 1 | # `actix-multipart-derive` 2 | 3 | > The derive macro implementation for actix-multipart-derive. 4 | 5 | 6 | 7 | [![crates.io](https://img.shields.io/crates/v/actix-multipart-derive?label=latest)](https://crates.io/crates/actix-multipart-derive) 8 | [![Documentation](https://docs.rs/actix-multipart-derive/badge.svg?version=0.7.0)](https://docs.rs/actix-multipart-derive/0.7.0) 9 | ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) 10 | ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart-derive.svg) 11 |
12 | [![dependency status](https://deps.rs/crate/actix-multipart-derive/0.7.0/status.svg)](https://deps.rs/crate/actix-multipart-derive/0.7.0) 13 | [![Download](https://img.shields.io/crates/d/actix-multipart-derive.svg)](https://crates.io/crates/actix-multipart-derive) 14 | [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) 15 | 16 | 17 | -------------------------------------------------------------------------------- /actix-multipart-derive/tests/trybuild.rs: -------------------------------------------------------------------------------- 1 | #[rustversion_msrv::msrv] 2 | #[test] 3 | fn compile_macros() { 4 | let t = trybuild::TestCases::new(); 5 | 6 | t.pass("tests/trybuild/all-required.rs"); 7 | t.pass("tests/trybuild/optional-and-list.rs"); 8 | t.pass("tests/trybuild/rename.rs"); 9 | t.pass("tests/trybuild/deny-unknown.rs"); 10 | 11 | t.pass("tests/trybuild/deny-duplicates.rs"); 12 | t.compile_fail("tests/trybuild/deny-parse-fail.rs"); 13 | 14 | t.pass("tests/trybuild/size-limits.rs"); 15 | t.compile_fail("tests/trybuild/size-limit-parse-fail.rs"); 16 | } 17 | -------------------------------------------------------------------------------- /actix-multipart-derive/tests/trybuild/all-required.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, Responder}; 2 | 3 | use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm}; 4 | 5 | #[derive(Debug, MultipartForm)] 6 | struct ImageUpload { 7 | description: Text, 8 | timestamp: Text, 9 | image: TempFile, 10 | } 11 | 12 | async fn handler(_form: MultipartForm) -> impl Responder { 13 | "Hello World!" 14 | } 15 | 16 | #[actix_web::main] 17 | async fn main() { 18 | App::new().default_service(web::to(handler)); 19 | } 20 | -------------------------------------------------------------------------------- /actix-multipart-derive/tests/trybuild/deny-duplicates.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, Responder}; 2 | 3 | use actix_multipart::form::MultipartForm; 4 | 5 | #[derive(MultipartForm)] 6 | #[multipart(duplicate_field = "deny")] 7 | struct Form {} 8 | 9 | async fn handler(_form: MultipartForm
) -> impl Responder { 10 | "Hello World!" 11 | } 12 | 13 | #[actix_web::main] 14 | async fn main() { 15 | App::new().default_service(web::to(handler)); 16 | } 17 | -------------------------------------------------------------------------------- /actix-multipart-derive/tests/trybuild/deny-parse-fail.rs: -------------------------------------------------------------------------------- 1 | use actix_multipart::form::MultipartForm; 2 | 3 | #[derive(MultipartForm)] 4 | #[multipart(duplicate_field = "no")] 5 | struct Form {} 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /actix-multipart-derive/tests/trybuild/deny-parse-fail.stderr: -------------------------------------------------------------------------------- 1 | error: Unknown literal value `no` 2 | --> tests/trybuild/deny-parse-fail.rs:4:31 3 | | 4 | 4 | #[multipart(duplicate_field = "no")] 5 | | ^^^^ 6 | -------------------------------------------------------------------------------- /actix-multipart-derive/tests/trybuild/deny-unknown.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, Responder}; 2 | 3 | use actix_multipart::form::MultipartForm; 4 | 5 | #[derive(MultipartForm)] 6 | #[multipart(deny_unknown_fields)] 7 | struct Form {} 8 | 9 | async fn handler(_form: MultipartForm) -> impl Responder { 10 | "Hello World!" 11 | } 12 | 13 | #[actix_web::main] 14 | async fn main() { 15 | App::new().default_service(web::to(handler)); 16 | } 17 | -------------------------------------------------------------------------------- /actix-multipart-derive/tests/trybuild/optional-and-list.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, Responder}; 2 | 3 | use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm}; 4 | 5 | #[derive(MultipartForm)] 6 | struct Form { 7 | category: Option>, 8 | files: Vec, 9 | } 10 | 11 | async fn handler(_form: MultipartForm) -> impl Responder { 12 | "Hello World!" 13 | } 14 | 15 | #[actix_web::main] 16 | async fn main() { 17 | App::new().default_service(web::to(handler)); 18 | } 19 | -------------------------------------------------------------------------------- /actix-multipart-derive/tests/trybuild/rename.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, Responder}; 2 | 3 | use actix_multipart::form::{tempfile::TempFile, MultipartForm}; 4 | 5 | #[derive(MultipartForm)] 6 | struct Form { 7 | #[multipart(rename = "files[]")] 8 | files: Vec, 9 | } 10 | 11 | async fn handler(_form: MultipartForm) -> impl Responder { 12 | "Hello World!" 13 | } 14 | 15 | #[actix_web::main] 16 | async fn main() { 17 | App::new().default_service(web::to(handler)); 18 | } 19 | -------------------------------------------------------------------------------- /actix-multipart-derive/tests/trybuild/size-limit-parse-fail.rs: -------------------------------------------------------------------------------- 1 | use actix_multipart::form::{text::Text, MultipartForm}; 2 | 3 | #[derive(MultipartForm)] 4 | struct Form { 5 | #[multipart(limit = "2 bytes")] 6 | description: Text, 7 | } 8 | 9 | #[derive(MultipartForm)] 10 | struct Form2 { 11 | #[multipart(limit = "2 megabytes")] 12 | description: Text, 13 | } 14 | 15 | #[derive(MultipartForm)] 16 | struct Form3 { 17 | #[multipart(limit = "four meters")] 18 | description: Text, 19 | } 20 | 21 | fn main() {} 22 | -------------------------------------------------------------------------------- /actix-multipart-derive/tests/trybuild/size-limit-parse-fail.stderr: -------------------------------------------------------------------------------- 1 | error: Could not parse size limit `2 bytes`: couldn't parse "bytes" into a known SI unit, couldn't parse unit of "bytes" 2 | --> tests/trybuild/size-limit-parse-fail.rs:6:5 3 | | 4 | 6 | description: Text, 5 | | ^^^^^^^^^^^ 6 | 7 | error: Could not parse size limit `2 megabytes`: couldn't parse "megabytes" into a known SI unit, couldn't parse unit of "megabytes" 8 | --> tests/trybuild/size-limit-parse-fail.rs:12:5 9 | | 10 | 12 | description: Text, 11 | | ^^^^^^^^^^^ 12 | 13 | error: Could not parse size limit `four meters`: couldn't parse "four meters" into a ByteSize, cannot parse float from empty string 14 | --> tests/trybuild/size-limit-parse-fail.rs:18:5 15 | | 16 | 18 | description: Text, 17 | | ^^^^^^^^^^^ 18 | -------------------------------------------------------------------------------- /actix-multipart-derive/tests/trybuild/size-limits.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, Responder}; 2 | 3 | use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm}; 4 | 5 | #[derive(MultipartForm)] 6 | struct Form { 7 | #[multipart(limit = "2 KiB")] 8 | description: Text, 9 | 10 | #[multipart(limit = "512 MiB")] 11 | files: Vec, 12 | } 13 | 14 | async fn handler(_form: MultipartForm) -> impl Responder { 15 | "Hello World!" 16 | } 17 | 18 | #[actix_web::main] 19 | async fn main() { 20 | App::new().default_service(web::to(handler)); 21 | } 22 | -------------------------------------------------------------------------------- /actix-multipart/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix-multipart" 3 | version = "0.7.2" 4 | authors = [ 5 | "Nikolay Kim ", 6 | "Jacob Halsey ", 7 | "Rob Ede ", 8 | ] 9 | description = "Multipart request & form support for Actix Web" 10 | keywords = ["http", "actix", "web", "multipart", "form"] 11 | homepage.workspace = true 12 | repository.workspace = true 13 | license.workspace = true 14 | edition.workspace = true 15 | 16 | [package.metadata.docs.rs] 17 | rustdoc-args = ["--cfg", "docsrs"] 18 | all-features = true 19 | 20 | [package.metadata.cargo_check_external_types] 21 | allowed_external_types = [ 22 | "actix_http::*", 23 | "actix_multipart_derive::*", 24 | "actix_utils::*", 25 | "actix_web::*", 26 | "bytes::*", 27 | "futures_core::*", 28 | "mime::*", 29 | "serde_json::*", 30 | "serde_plain::*", 31 | "serde::*", 32 | "tempfile::*", 33 | ] 34 | 35 | [features] 36 | default = ["tempfile", "derive"] 37 | derive = ["actix-multipart-derive"] 38 | tempfile = ["dep:tempfile", "tokio/fs"] 39 | 40 | [dependencies] 41 | actix-multipart-derive = { version = "=0.7.0", optional = true } 42 | actix-utils = "3" 43 | actix-web = { version = "4", default-features = false } 44 | 45 | derive_more = { version = "2", features = ["display", "error", "from"] } 46 | futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } 47 | futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } 48 | httparse = "1.3" 49 | local-waker = "0.1" 50 | log = "0.4" 51 | memchr = "2.5" 52 | mime = "0.3" 53 | rand = "0.9" 54 | serde = "1" 55 | serde_json = "1" 56 | serde_plain = "1" 57 | tempfile = { version = "3.4", optional = true } 58 | tokio = { version = "1.38.2", features = ["sync", "io-util"] } 59 | 60 | [dev-dependencies] 61 | actix-http = "3" 62 | actix-multipart-rfc7578 = "0.11" 63 | actix-rt = "2.2" 64 | actix-test = "0.1" 65 | actix-web = "4" 66 | assert_matches = "1" 67 | awc = "3" 68 | env_logger = "0.11" 69 | futures-test = "0.3" 70 | futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } 71 | multer = "3" 72 | tokio = { version = "1.38.2", features = ["sync"] } 73 | tokio-stream = "0.1" 74 | 75 | [lints] 76 | workspace = true 77 | -------------------------------------------------------------------------------- /actix-multipart/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /actix-multipart/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /actix-multipart/examples/form.rs: -------------------------------------------------------------------------------- 1 | use actix_multipart::form::{json::Json as MpJson, tempfile::TempFile, MultipartForm}; 2 | use actix_web::{middleware::Logger, post, App, HttpServer, Responder}; 3 | use serde::Deserialize; 4 | 5 | #[derive(Debug, Deserialize)] 6 | struct Metadata { 7 | name: String, 8 | } 9 | 10 | #[derive(Debug, MultipartForm)] 11 | struct UploadForm { 12 | #[multipart(limit = "100MB")] 13 | file: TempFile, 14 | json: MpJson, 15 | } 16 | 17 | #[post("/videos")] 18 | async fn post_video(MultipartForm(form): MultipartForm) -> impl Responder { 19 | format!( 20 | "Uploaded file {}, with size: {}\ntemporary file ({}) was deleted\n", 21 | form.json.name, 22 | form.file.size, 23 | form.file.file.path().display(), 24 | ) 25 | } 26 | 27 | #[actix_web::main] 28 | async fn main() -> std::io::Result<()> { 29 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 30 | 31 | HttpServer::new(move || App::new().service(post_video).wrap(Logger::default())) 32 | .workers(2) 33 | .bind(("127.0.0.1", 8080))? 34 | .run() 35 | .await 36 | } 37 | -------------------------------------------------------------------------------- /actix-multipart/src/extractor.rs: -------------------------------------------------------------------------------- 1 | use actix_utils::future::{ready, Ready}; 2 | use actix_web::{dev::Payload, Error, FromRequest, HttpRequest}; 3 | 4 | use crate::multipart::Multipart; 5 | 6 | /// Extract request's payload as multipart stream. 7 | /// 8 | /// Content-type: multipart/*; 9 | /// 10 | /// # Examples 11 | /// 12 | /// ``` 13 | /// use actix_web::{web, HttpResponse}; 14 | /// use actix_multipart::Multipart; 15 | /// use futures_util::StreamExt as _; 16 | /// 17 | /// async fn index(mut payload: Multipart) -> actix_web::Result { 18 | /// // iterate over multipart stream 19 | /// while let Some(item) = payload.next().await { 20 | /// let mut field = item?; 21 | /// 22 | /// // Field in turn is stream of *Bytes* object 23 | /// while let Some(chunk) = field.next().await { 24 | /// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk?)); 25 | /// } 26 | /// } 27 | /// 28 | /// Ok(HttpResponse::Ok().finish()) 29 | /// } 30 | /// ``` 31 | impl FromRequest for Multipart { 32 | type Error = Error; 33 | type Future = Ready>; 34 | 35 | #[inline] 36 | fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { 37 | ready(Ok(Multipart::from_req(req, payload))) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /actix-multipart/src/form/bytes.rs: -------------------------------------------------------------------------------- 1 | //! Reads a field into memory. 2 | 3 | use actix_web::{web::BytesMut, HttpRequest}; 4 | use futures_core::future::LocalBoxFuture; 5 | use futures_util::TryStreamExt as _; 6 | use mime::Mime; 7 | 8 | use crate::{ 9 | form::{FieldReader, Limits}, 10 | Field, MultipartError, 11 | }; 12 | 13 | /// Read the field into memory. 14 | #[derive(Debug)] 15 | pub struct Bytes { 16 | /// The data. 17 | pub data: actix_web::web::Bytes, 18 | 19 | /// The value of the `Content-Type` header. 20 | pub content_type: Option, 21 | 22 | /// The `filename` value in the `Content-Disposition` header. 23 | pub file_name: Option, 24 | } 25 | 26 | impl<'t> FieldReader<'t> for Bytes { 27 | type Future = LocalBoxFuture<'t, Result>; 28 | 29 | fn read_field(_: &'t HttpRequest, mut field: Field, limits: &'t mut Limits) -> Self::Future { 30 | Box::pin(async move { 31 | let mut buf = BytesMut::with_capacity(131_072); 32 | 33 | while let Some(chunk) = field.try_next().await? { 34 | limits.try_consume_limits(chunk.len(), true)?; 35 | buf.extend(chunk); 36 | } 37 | 38 | Ok(Bytes { 39 | data: buf.freeze(), 40 | content_type: field.content_type().map(ToOwned::to_owned), 41 | file_name: field 42 | .content_disposition() 43 | .expect("multipart form fields should have a content-disposition header") 44 | .get_filename() 45 | .map(ToOwned::to_owned), 46 | }) 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /actix-multipart/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Multipart request & form support for Actix Web. 2 | //! 3 | //! The [`Multipart`] extractor aims to support all kinds of `multipart/*` requests, including 4 | //! `multipart/form-data`, `multipart/related` and `multipart/mixed`. This is a lower-level 5 | //! extractor which supports reading [multipart fields](Field), in the order they are sent by the 6 | //! client. 7 | //! 8 | //! Due to additional requirements for `multipart/form-data` requests, the higher level 9 | //! [`MultipartForm`] extractor and derive macro only supports this media type. 10 | //! 11 | //! # Examples 12 | //! 13 | //! ```no_run 14 | //! use actix_web::{post, App, HttpServer, Responder}; 15 | //! 16 | //! use actix_multipart::form::{json::Json as MpJson, tempfile::TempFile, MultipartForm}; 17 | //! use serde::Deserialize; 18 | //! 19 | //! #[derive(Debug, Deserialize)] 20 | //! struct Metadata { 21 | //! name: String, 22 | //! } 23 | //! 24 | //! #[derive(Debug, MultipartForm)] 25 | //! struct UploadForm { 26 | //! #[multipart(limit = "100MB")] 27 | //! file: TempFile, 28 | //! json: MpJson, 29 | //! } 30 | //! 31 | //! #[post("/videos")] 32 | //! pub async fn post_video(MultipartForm(form): MultipartForm) -> impl Responder { 33 | //! format!( 34 | //! "Uploaded file {}, with size: {}", 35 | //! form.json.name, form.file.size 36 | //! ) 37 | //! } 38 | //! 39 | //! #[actix_web::main] 40 | //! async fn main() -> std::io::Result<()> { 41 | //! HttpServer::new(move || App::new().service(post_video)) 42 | //! .bind(("127.0.0.1", 8080))? 43 | //! .run() 44 | //! .await 45 | //! } 46 | //! ``` 47 | //! 48 | //! cURL request: 49 | //! 50 | //! ```sh 51 | //! curl -v --request POST \ 52 | //! --url http://localhost:8080/videos \ 53 | //! -F 'json={"name": "Cargo.lock"};type=application/json' \ 54 | //! -F file=@./Cargo.lock 55 | //! ``` 56 | //! 57 | //! [`MultipartForm`]: struct@form::MultipartForm 58 | 59 | #![doc(html_logo_url = "https://actix.rs/img/logo.png")] 60 | #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] 61 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 62 | 63 | // This allows us to use the actix_multipart_derive within this crate's tests 64 | #[cfg(test)] 65 | extern crate self as actix_multipart; 66 | 67 | mod error; 68 | mod extractor; 69 | pub(crate) mod field; 70 | pub mod form; 71 | mod multipart; 72 | pub(crate) mod payload; 73 | pub(crate) mod safety; 74 | pub mod test; 75 | 76 | pub use self::{ 77 | error::Error as MultipartError, 78 | field::{Field, LimitExceeded}, 79 | multipart::Multipart, 80 | }; 81 | -------------------------------------------------------------------------------- /actix-multipart/src/safety.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::Cell, marker::PhantomData, rc::Rc, task}; 2 | 3 | use local_waker::LocalWaker; 4 | 5 | /// Counter. It tracks of number of clones of payloads and give access to payload only to top most. 6 | /// 7 | /// - When dropped, parent task is awakened. This is to support the case where `Field` is dropped in 8 | /// a separate task than `Multipart`. 9 | /// - Assumes that parent owners don't move to different tasks; only the top-most is allowed to. 10 | /// - If dropped and is not top most owner, is_clean flag is set to false. 11 | #[derive(Debug)] 12 | pub(crate) struct Safety { 13 | task: LocalWaker, 14 | level: usize, 15 | payload: Rc>, 16 | clean: Rc>, 17 | } 18 | 19 | impl Safety { 20 | pub(crate) fn new() -> Safety { 21 | let payload = Rc::new(PhantomData); 22 | Safety { 23 | task: LocalWaker::new(), 24 | level: Rc::strong_count(&payload), 25 | clean: Rc::new(Cell::new(true)), 26 | payload, 27 | } 28 | } 29 | 30 | pub(crate) fn current(&self) -> bool { 31 | Rc::strong_count(&self.payload) == self.level && self.clean.get() 32 | } 33 | 34 | pub(crate) fn is_clean(&self) -> bool { 35 | self.clean.get() 36 | } 37 | 38 | pub(crate) fn clone(&self, cx: &task::Context<'_>) -> Safety { 39 | let payload = Rc::clone(&self.payload); 40 | let s = Safety { 41 | task: LocalWaker::new(), 42 | level: Rc::strong_count(&payload), 43 | clean: self.clean.clone(), 44 | payload, 45 | }; 46 | s.task.register(cx.waker()); 47 | s 48 | } 49 | } 50 | 51 | impl Drop for Safety { 52 | fn drop(&mut self) { 53 | if Rc::strong_count(&self.payload) != self.level { 54 | // Multipart dropped leaving a Field 55 | self.clean.set(false); 56 | } 57 | 58 | self.task.wake(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /actix-router/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix-router" 3 | version = "0.5.3" 4 | authors = [ 5 | "Nikolay Kim ", 6 | "Ali MJ Al-Nasrawy ", 7 | "Rob Ede ", 8 | ] 9 | description = "Resource path matching and router" 10 | keywords = ["actix", "router", "routing"] 11 | repository = "https://github.com/actix/actix-web" 12 | license = "MIT OR Apache-2.0" 13 | edition = "2021" 14 | 15 | [package.metadata.cargo_check_external_types] 16 | allowed_external_types = ["http::*", "serde::*"] 17 | 18 | [features] 19 | default = ["http", "unicode"] 20 | http = ["dep:http"] 21 | unicode = ["dep:regex"] 22 | 23 | [dependencies] 24 | bytestring = ">=0.1.5, <2" 25 | cfg-if = "1" 26 | http = { version = "0.2.7", optional = true } 27 | regex = { version = "1.5", optional = true } 28 | regex-lite = "0.1" 29 | serde = "1" 30 | tracing = { version = "0.1.30", default-features = false, features = ["log"] } 31 | 32 | [dev-dependencies] 33 | criterion = { version = "0.5", features = ["html_reports"] } 34 | http = "0.2.7" 35 | percent-encoding = "2.1" 36 | serde = { version = "1", features = ["derive"] } 37 | 38 | [lints] 39 | workspace = true 40 | 41 | [[bench]] 42 | name = "router" 43 | harness = false 44 | required-features = ["unicode"] 45 | 46 | [[bench]] 47 | name = "quoter" 48 | harness = false 49 | -------------------------------------------------------------------------------- /actix-router/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /actix-router/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /actix-router/README.md: -------------------------------------------------------------------------------- 1 | # `actix-router` 2 | 3 | 4 | 5 | [![crates.io](https://img.shields.io/crates/v/actix-router?label=latest)](https://crates.io/crates/actix-router) 6 | [![Documentation](https://docs.rs/actix-router/badge.svg?version=0.5.3)](https://docs.rs/actix-router/0.5.3) 7 | ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) 8 | ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-router.svg) 9 |
10 | [![dependency status](https://deps.rs/crate/actix-router/0.5.3/status.svg)](https://deps.rs/crate/actix-router/0.5.3) 11 | [![Download](https://img.shields.io/crates/d/actix-router.svg)](https://crates.io/crates/actix-router) 12 | [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) 13 | 14 | 15 | 16 | 17 | 18 | Resource path matching and router. 19 | 20 | 21 | -------------------------------------------------------------------------------- /actix-router/benches/quoter.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, fmt::Write as _}; 2 | 3 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 4 | 5 | fn compare_quoters(c: &mut Criterion) { 6 | let mut group = c.benchmark_group("Compare Quoters"); 7 | 8 | let quoter = actix_router::Quoter::new(b"", b""); 9 | let path_quoted = (0..=0x7f).fold(String::new(), |mut buf, c| { 10 | write!(&mut buf, "%{:02X}", c).unwrap(); 11 | buf 12 | }); 13 | let path_unquoted = ('\u{00}'..='\u{7f}').collect::(); 14 | 15 | group.bench_function("quoter_unquoted", |b| { 16 | b.iter(|| { 17 | for _ in 0..10 { 18 | black_box(quoter.requote(path_unquoted.as_bytes())); 19 | } 20 | }); 21 | }); 22 | 23 | group.bench_function("percent_encode_unquoted", |b| { 24 | b.iter(|| { 25 | for _ in 0..10 { 26 | let decode = percent_encoding::percent_decode(path_unquoted.as_bytes()); 27 | black_box(Into::>::into(decode)); 28 | } 29 | }); 30 | }); 31 | 32 | group.bench_function("quoter_quoted", |b| { 33 | b.iter(|| { 34 | for _ in 0..10 { 35 | black_box(quoter.requote(path_quoted.as_bytes())); 36 | } 37 | }); 38 | }); 39 | 40 | group.bench_function("percent_encode_quoted", |b| { 41 | b.iter(|| { 42 | for _ in 0..10 { 43 | let decode = percent_encoding::percent_decode(path_quoted.as_bytes()); 44 | black_box(Into::>::into(decode)); 45 | } 46 | }); 47 | }); 48 | 49 | group.finish(); 50 | } 51 | 52 | criterion_group!(benches, compare_quoters); 53 | criterion_main!(benches); 54 | -------------------------------------------------------------------------------- /actix-router/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Resource path matching and router. 2 | 3 | #![doc(html_logo_url = "https://actix.rs/img/logo.png")] 4 | #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] 5 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 6 | 7 | mod de; 8 | mod path; 9 | mod pattern; 10 | mod quoter; 11 | mod regex_set; 12 | mod resource; 13 | mod resource_path; 14 | mod router; 15 | 16 | #[cfg(feature = "http")] 17 | mod url; 18 | 19 | #[cfg(feature = "http")] 20 | pub use self::url::Url; 21 | pub use self::{ 22 | de::PathDeserializer, 23 | path::Path, 24 | pattern::{IntoPatterns, Patterns}, 25 | quoter::Quoter, 26 | resource::ResourceDef, 27 | resource_path::{Resource, ResourcePath}, 28 | router::{ResourceId, Router, RouterBuilder}, 29 | }; 30 | -------------------------------------------------------------------------------- /actix-router/src/pattern.rs: -------------------------------------------------------------------------------- 1 | /// One or many patterns. 2 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 3 | pub enum Patterns { 4 | Single(String), 5 | List(Vec), 6 | } 7 | 8 | impl Patterns { 9 | pub fn is_empty(&self) -> bool { 10 | match self { 11 | Patterns::Single(_) => false, 12 | Patterns::List(pats) => pats.is_empty(), 13 | } 14 | } 15 | } 16 | 17 | /// Helper trait for type that could be converted to one or more path patterns. 18 | pub trait IntoPatterns { 19 | fn patterns(&self) -> Patterns; 20 | } 21 | 22 | impl IntoPatterns for String { 23 | fn patterns(&self) -> Patterns { 24 | Patterns::Single(self.clone()) 25 | } 26 | } 27 | 28 | impl IntoPatterns for &String { 29 | fn patterns(&self) -> Patterns { 30 | (*self).patterns() 31 | } 32 | } 33 | 34 | impl IntoPatterns for str { 35 | fn patterns(&self) -> Patterns { 36 | Patterns::Single(self.to_owned()) 37 | } 38 | } 39 | 40 | impl IntoPatterns for &str { 41 | fn patterns(&self) -> Patterns { 42 | (*self).patterns() 43 | } 44 | } 45 | 46 | impl IntoPatterns for bytestring::ByteString { 47 | fn patterns(&self) -> Patterns { 48 | Patterns::Single(self.to_string()) 49 | } 50 | } 51 | 52 | impl IntoPatterns for Patterns { 53 | fn patterns(&self) -> Patterns { 54 | self.clone() 55 | } 56 | } 57 | 58 | impl> IntoPatterns for Vec { 59 | fn patterns(&self) -> Patterns { 60 | let mut patterns = self.iter().map(|v| v.as_ref().to_owned()); 61 | 62 | match patterns.size_hint() { 63 | (1, _) => Patterns::Single(patterns.next().unwrap()), 64 | _ => Patterns::List(patterns.collect()), 65 | } 66 | } 67 | } 68 | 69 | macro_rules! array_patterns_single (($tp:ty) => { 70 | impl IntoPatterns for [$tp; 1] { 71 | fn patterns(&self) -> Patterns { 72 | Patterns::Single(self[0].to_owned()) 73 | } 74 | } 75 | }); 76 | 77 | macro_rules! array_patterns_multiple (($tp:ty, $str_fn:expr, $($num:tt) +) => { 78 | // for each array length specified in space-separated $num 79 | $( 80 | impl IntoPatterns for [$tp; $num] { 81 | fn patterns(&self) -> Patterns { 82 | Patterns::List(self.iter().map($str_fn).collect()) 83 | } 84 | } 85 | )+ 86 | }); 87 | 88 | array_patterns_single!(&str); 89 | array_patterns_multiple!(&str, |&v| v.to_owned(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16); 90 | 91 | array_patterns_single!(String); 92 | array_patterns_multiple!(String, |v| v.clone(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16); 93 | -------------------------------------------------------------------------------- /actix-router/src/regex_set.rs: -------------------------------------------------------------------------------- 1 | //! Abstraction over `regex` and `regex-lite` depending on whether we have `unicode` crate feature 2 | //! enabled. 3 | 4 | use cfg_if::cfg_if; 5 | #[cfg(feature = "unicode")] 6 | pub(crate) use regex::{escape, Regex}; 7 | #[cfg(not(feature = "unicode"))] 8 | pub(crate) use regex_lite::{escape, Regex}; 9 | 10 | #[cfg(feature = "unicode")] 11 | #[derive(Debug, Clone)] 12 | pub(crate) struct RegexSet(regex::RegexSet); 13 | 14 | #[cfg(not(feature = "unicode"))] 15 | #[derive(Debug, Clone)] 16 | pub(crate) struct RegexSet(Vec); 17 | 18 | impl RegexSet { 19 | /// Create a new regex set. 20 | /// 21 | /// # Panics 22 | /// 23 | /// Panics if any path patterns are malformed. 24 | pub(crate) fn new(re_set: Vec) -> Self { 25 | cfg_if! { 26 | if #[cfg(feature = "unicode")] { 27 | Self(regex::RegexSet::new(re_set).unwrap()) 28 | } else { 29 | Self(re_set.iter().map(|re| Regex::new(re).unwrap()).collect()) 30 | } 31 | } 32 | } 33 | 34 | /// Create a new empty regex set. 35 | pub(crate) fn empty() -> Self { 36 | cfg_if! { 37 | if #[cfg(feature = "unicode")] { 38 | Self(regex::RegexSet::empty()) 39 | } else { 40 | Self(Vec::new()) 41 | } 42 | } 43 | } 44 | 45 | /// Returns true if regex set matches `path`. 46 | pub(crate) fn is_match(&self, path: &str) -> bool { 47 | cfg_if! { 48 | if #[cfg(feature = "unicode")] { 49 | self.0.is_match(path) 50 | } else { 51 | self.0.iter().any(|re| re.is_match(path)) 52 | } 53 | } 54 | } 55 | 56 | /// Returns index within `path` of first match. 57 | pub(crate) fn first_match_idx(&self, path: &str) -> Option { 58 | cfg_if! { 59 | if #[cfg(feature = "unicode")] { 60 | self.0.matches(path).into_iter().next() 61 | } else { 62 | Some(self.0.iter().enumerate().find(|(_, re)| re.is_match(path))?.0) 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /actix-router/src/resource_path.rs: -------------------------------------------------------------------------------- 1 | use crate::Path; 2 | 3 | // TODO: this trait is necessary, document it 4 | // see impl Resource for ServiceRequest 5 | pub trait Resource { 6 | /// Type of resource's path returned in `resource_path`. 7 | type Path: ResourcePath; 8 | 9 | fn resource_path(&mut self) -> &mut Path; 10 | } 11 | 12 | pub trait ResourcePath { 13 | fn path(&self) -> &str; 14 | } 15 | 16 | impl ResourcePath for String { 17 | fn path(&self) -> &str { 18 | self.as_str() 19 | } 20 | } 21 | 22 | impl ResourcePath for &str { 23 | fn path(&self) -> &str { 24 | self 25 | } 26 | } 27 | 28 | impl ResourcePath for bytestring::ByteString { 29 | fn path(&self) -> &str { 30 | self 31 | } 32 | } 33 | 34 | #[cfg(feature = "http")] 35 | impl ResourcePath for http::Uri { 36 | fn path(&self) -> &str { 37 | self.path() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /actix-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix-test" 3 | version = "0.1.5" 4 | authors = ["Nikolay Kim ", "Rob Ede "] 5 | description = "Integration testing tools for Actix Web applications" 6 | keywords = ["http", "web", "framework", "async", "futures"] 7 | homepage = "https://actix.rs" 8 | repository = "https://github.com/actix/actix-web" 9 | categories = [ 10 | "network-programming", 11 | "asynchronous", 12 | "web-programming::http-server", 13 | "web-programming::websocket", 14 | ] 15 | license = "MIT OR Apache-2.0" 16 | edition = "2021" 17 | 18 | [package.metadata.cargo_check_external_types] 19 | allowed_external_types = [ 20 | "actix_codec::*", 21 | "actix_http_test::*", 22 | "actix_http::*", 23 | "actix_service::*", 24 | "actix_web::*", 25 | "awc::*", 26 | "bytes::*", 27 | "futures_core::*", 28 | "http::*", 29 | "openssl::*", 30 | "rustls::*", 31 | "tokio::*", 32 | ] 33 | 34 | [features] 35 | default = [] 36 | 37 | # TLS via Rustls v0.20 38 | rustls = ["rustls-0_20"] 39 | # TLS via Rustls v0.20 40 | rustls-0_20 = ["tls-rustls-0_20", "actix-http/rustls-0_20", "awc/rustls-0_20"] 41 | # TLS via Rustls v0.21 42 | rustls-0_21 = ["tls-rustls-0_21", "actix-http/rustls-0_21", "awc/rustls-0_21"] 43 | # TLS via Rustls v0.22 44 | rustls-0_22 = ["tls-rustls-0_22", "actix-http/rustls-0_22", "awc/rustls-0_22-webpki-roots"] 45 | # TLS via Rustls v0.23 46 | rustls-0_23 = ["tls-rustls-0_23", "actix-http/rustls-0_23", "awc/rustls-0_23-webpki-roots"] 47 | 48 | # TLS via OpenSSL 49 | openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] 50 | 51 | [dependencies] 52 | actix-codec = "0.5" 53 | actix-http = "3.7" 54 | actix-http-test = "3" 55 | actix-rt = "2.1" 56 | actix-service = "2" 57 | actix-utils = "3" 58 | actix-web = { version = "4.6", default-features = false, features = ["cookies"] } 59 | awc = { version = "3.5", default-features = false, features = ["cookies"] } 60 | 61 | futures-core = { version = "0.3.17", default-features = false, features = ["std"] } 62 | futures-util = { version = "0.3.17", default-features = false, features = [] } 63 | log = "0.4" 64 | serde = { version = "1", features = ["derive"] } 65 | serde_json = "1" 66 | serde_urlencoded = "0.7" 67 | tls-openssl = { package = "openssl", version = "0.10.55", optional = true } 68 | tls-rustls-0_20 = { package = "rustls", version = "0.20", optional = true } 69 | tls-rustls-0_21 = { package = "rustls", version = "0.21", optional = true } 70 | tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true } 71 | tls-rustls-0_23 = { package = "rustls", version = "0.23", default-features = false, optional = true } 72 | tokio = { version = "1.38.2", features = ["sync"] } 73 | 74 | [lints] 75 | workspace = true 76 | -------------------------------------------------------------------------------- /actix-test/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /actix-test/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /actix-test/README.md: -------------------------------------------------------------------------------- 1 | # `actix-test` 2 | 3 | 4 | 5 | [![crates.io](https://img.shields.io/crates/v/actix-test?label=latest)](https://crates.io/crates/actix-test) 6 | [![Documentation](https://docs.rs/actix-test/badge.svg?version=0.1.5)](https://docs.rs/actix-test/0.1.5) 7 | ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) 8 | ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-test.svg) 9 |
10 | [![dependency status](https://deps.rs/crate/actix-test/0.1.5/status.svg)](https://deps.rs/crate/actix-test/0.1.5) 11 | [![Download](https://img.shields.io/crates/d/actix-test.svg)](https://crates.io/crates/actix-test) 12 | [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) 13 | 14 | 15 | 16 | 17 | 18 | Integration testing tools for Actix Web applications. 19 | 20 | The main integration testing tool is [`TestServer`]. It spawns a real HTTP server on an unused port and provides methods that use a real HTTP client. Therefore, it is much closer to real-world cases than using `init_service`, which skips HTTP encoding and decoding. 21 | 22 | ## Examples 23 | 24 | ```rust 25 | use actix_web::{get, web, test, App, HttpResponse, Error, Responder}; 26 | 27 | #[get("/")] 28 | async fn my_handler() -> Result { 29 | Ok(HttpResponse::Ok()) 30 | } 31 | 32 | #[actix_rt::test] 33 | async fn test_example() { 34 | let srv = actix_test::start(|| 35 | App::new().service(my_handler) 36 | ); 37 | 38 | let req = srv.get("/"); 39 | let res = req.send().await.unwrap(); 40 | 41 | assert!(res.status().is_success()); 42 | } 43 | ``` 44 | 45 | 46 | -------------------------------------------------------------------------------- /actix-web-actors/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix-web-actors" 3 | version = "4.3.1+deprecated" 4 | authors = ["Nikolay Kim "] 5 | description = "Actix actors support for Actix Web" 6 | keywords = ["actix", "http", "web", "framework", "async"] 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | edition.workspace = true 11 | rust-version.workspace = true 12 | 13 | [package.metadata.cargo_check_external_types] 14 | allowed_external_types = [ 15 | "actix::*", 16 | "actix_http::*", 17 | "actix_web::*", 18 | "bytes::*", 19 | "bytestring::*", 20 | "futures_core::*", 21 | ] 22 | 23 | [dependencies] 24 | actix = { version = ">=0.12, <0.14", default-features = false } 25 | actix-codec = "0.5" 26 | actix-http = "3" 27 | actix-web = { version = "4", default-features = false } 28 | 29 | bytes = "1" 30 | bytestring = "1" 31 | futures-core = { version = "0.3.17", default-features = false } 32 | pin-project-lite = "0.2" 33 | tokio = { version = "1.38.2", features = ["sync"] } 34 | tokio-util = { version = "0.7", features = ["codec"] } 35 | 36 | [dev-dependencies] 37 | actix-rt = "2.2" 38 | actix-test = "0.1" 39 | actix-web = { version = "4", features = ["macros"] } 40 | awc = { version = "3", default-features = false } 41 | 42 | env_logger = "0.11" 43 | futures-util = { version = "0.3.17", default-features = false, features = ["std"] } 44 | mime = "0.3" 45 | 46 | [lints] 47 | workspace = true 48 | -------------------------------------------------------------------------------- /actix-web-actors/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /actix-web-actors/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /actix-web-actors/README.md: -------------------------------------------------------------------------------- 1 | # `actix-web-actors` 2 | 3 | > Actix actors support for Actix Web. 4 | > 5 | > This crate is deprecated. Migrate to [`actix-ws`](https://crates.io/crates/actix-ws). 6 | 7 | 8 | 9 | [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) 10 | [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.3.1)](https://docs.rs/actix-web-actors/4.3.1) 11 | ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) 12 | ![License](https://img.shields.io/crates/l/actix-web-actors.svg) 13 |
14 | ![maintenance-status](https://img.shields.io/badge/maintenance-deprecated-red.svg) 15 | [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) 16 | [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) 17 | 18 | 19 | -------------------------------------------------------------------------------- /actix-web-actors/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Actix actors support for Actix Web. 2 | //! 3 | //! This crate is deprecated. Migrate to [`actix-ws`](https://crates.io/crates/actix-ws). 4 | //! 5 | //! # Examples 6 | //! 7 | //! ```no_run 8 | //! use actix::{Actor, StreamHandler}; 9 | //! use actix_web::{get, web, App, Error, HttpRequest, HttpResponse, HttpServer}; 10 | //! use actix_web_actors::ws; 11 | //! 12 | //! /// Define Websocket actor 13 | //! struct MyWs; 14 | //! 15 | //! impl Actor for MyWs { 16 | //! type Context = ws::WebsocketContext; 17 | //! } 18 | //! 19 | //! /// Handler for ws::Message message 20 | //! impl StreamHandler> for MyWs { 21 | //! fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { 22 | //! match msg { 23 | //! Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), 24 | //! Ok(ws::Message::Text(text)) => ctx.text(text), 25 | //! Ok(ws::Message::Binary(bin)) => ctx.binary(bin), 26 | //! _ => (), 27 | //! } 28 | //! } 29 | //! } 30 | //! 31 | //! #[get("/ws")] 32 | //! async fn index(req: HttpRequest, stream: web::Payload) -> Result { 33 | //! ws::start(MyWs, &req, stream) 34 | //! } 35 | //! 36 | //! #[actix_web::main] 37 | //! async fn main() -> std::io::Result<()> { 38 | //! HttpServer::new(|| App::new().service(index)) 39 | //! .bind(("127.0.0.1", 8080))? 40 | //! .run() 41 | //! .await 42 | //! } 43 | //! ``` 44 | //! 45 | //! # Documentation & Community Resources 46 | //! In addition to this API documentation, several other resources are available: 47 | //! 48 | //! * [Website & User Guide](https://actix.rs/) 49 | //! * [Documentation for `actix_web`](actix_web) 50 | //! * [Examples Repository](https://github.com/actix/examples) 51 | //! * [Community Chat on Discord](https://discord.gg/NWpN5mmg3x) 52 | //! 53 | //! To get started navigating the API docs, you may consider looking at the following pages first: 54 | //! 55 | //! * [`ws`]: This module provides actor support for WebSockets. 56 | //! 57 | //! * [`HttpContext`]: This struct provides actor support for streaming HTTP responses. 58 | //! 59 | 60 | #![doc(html_logo_url = "https://actix.rs/img/logo.png")] 61 | #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] 62 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 63 | 64 | mod context; 65 | pub mod ws; 66 | 67 | pub use self::context::HttpContext; 68 | -------------------------------------------------------------------------------- /actix-web-codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix-web-codegen" 3 | version = "4.3.0" 4 | description = "Routing and runtime macros for Actix Web" 5 | authors = ["Nikolay Kim ", "Rob Ede "] 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | edition.workspace = true 10 | rust-version.workspace = true 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [features] 16 | default = ["compat-routing-macros-force-pub"] 17 | compat-routing-macros-force-pub = [] 18 | 19 | [dependencies] 20 | actix-router = { version = "0.5", default-features = false } 21 | proc-macro2 = "1" 22 | quote = "1" 23 | syn = { version = "2", features = ["full", "extra-traits"] } 24 | 25 | [dev-dependencies] 26 | actix-macros = "0.2.4" 27 | actix-rt = "2.2" 28 | actix-test = "0.1" 29 | actix-utils = "3" 30 | actix-web = "4" 31 | 32 | futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } 33 | rustversion-msrv = "0.100" 34 | trybuild = "1" 35 | 36 | [lints] 37 | workspace = true 38 | -------------------------------------------------------------------------------- /actix-web-codegen/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /actix-web-codegen/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /actix-web-codegen/README.md: -------------------------------------------------------------------------------- 1 | # `actix-web-codegen` 2 | 3 | > Routing and runtime macros for Actix Web. 4 | 5 | 6 | 7 | [![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) 8 | [![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.3.0)](https://docs.rs/actix-web-codegen/4.3.0) 9 | ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) 10 | ![License](https://img.shields.io/crates/l/actix-web-codegen.svg) 11 |
12 | [![dependency status](https://deps.rs/crate/actix-web-codegen/4.3.0/status.svg)](https://deps.rs/crate/actix-web-codegen/4.3.0) 13 | [![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) 14 | [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) 15 | 16 | 17 | 18 | ## Compile Testing 19 | 20 | Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this. 21 | 22 | [`trybuild`]: https://github.com/dtolnay/trybuild 23 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild.rs: -------------------------------------------------------------------------------- 1 | #[rustversion_msrv::msrv] 2 | #[test] 3 | fn compile_macros() { 4 | let t = trybuild::TestCases::new(); 5 | 6 | t.pass("tests/trybuild/simple.rs"); 7 | t.compile_fail("tests/trybuild/simple-fail.rs"); 8 | 9 | t.pass("tests/trybuild/route-ok.rs"); 10 | t.compile_fail("tests/trybuild/route-missing-method-fail.rs"); 11 | t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs"); 12 | t.compile_fail("tests/trybuild/route-malformed-path-fail.rs"); 13 | 14 | t.pass("tests/trybuild/route-custom-method.rs"); 15 | t.compile_fail("tests/trybuild/route-custom-lowercase.rs"); 16 | 17 | t.pass("tests/trybuild/routes-ok.rs"); 18 | t.compile_fail("tests/trybuild/routes-missing-method-fail.rs"); 19 | t.compile_fail("tests/trybuild/routes-missing-args-fail.rs"); 20 | 21 | t.compile_fail("tests/trybuild/scope-on-handler.rs"); 22 | t.compile_fail("tests/trybuild/scope-missing-args.rs"); 23 | t.compile_fail("tests/trybuild/scope-invalid-args.rs"); 24 | t.compile_fail("tests/trybuild/scope-trailing-slash.rs"); 25 | 26 | t.pass("tests/trybuild/docstring-ok.rs"); 27 | 28 | t.pass("tests/trybuild/test-runtime.rs"); 29 | } 30 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/docstring-ok.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{Responder, HttpResponse, App}; 2 | use actix_web_codegen::*; 3 | 4 | /// doc comments shouldn't break anything 5 | #[get("/")] 6 | async fn index() -> impl Responder { 7 | HttpResponse::Ok() 8 | } 9 | 10 | #[actix_web::main] 11 | async fn main() { 12 | let srv = actix_test::start(|| App::new().service(index)); 13 | 14 | let request = srv.get("/"); 15 | let response = request.send().await.unwrap(); 16 | assert!(response.status().is_success()); 17 | } 18 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/route-custom-lowercase.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::*; 2 | use actix_web::http::Method; 3 | use std::str::FromStr; 4 | 5 | #[route("/", method = "hello")] 6 | async fn index() -> String { 7 | "Hello World!".to_owned() 8 | } 9 | 10 | #[actix_web::main] 11 | async fn main() { 12 | use actix_web::App; 13 | 14 | let srv = actix_test::start(|| App::new().service(index)); 15 | 16 | let request = srv.request(Method::from_str("hello").unwrap(), srv.url("/")); 17 | let response = request.send().await.unwrap(); 18 | assert!(response.status().is_success()); 19 | } 20 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/route-custom-lowercase.stderr: -------------------------------------------------------------------------------- 1 | error: HTTP method must be uppercase: `hello` 2 | --> tests/trybuild/route-custom-lowercase.rs:5:23 3 | | 4 | 5 | #[route("/", method = "hello")] 5 | | ^^^^^^^ 6 | 7 | error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied 8 | --> tests/trybuild/route-custom-lowercase.rs:14:55 9 | | 10 | 14 | let srv = actix_test::start(|| App::new().service(index)); 11 | | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future {index}` 12 | | | 13 | | required by a bound introduced by this call 14 | | 15 | = help: the following other types implement trait `HttpServiceFactory`: 16 | Resource 17 | actix_web::Scope 18 | Vec 19 | Redirect 20 | (A,) 21 | (A, B) 22 | (A, B, C) 23 | (A, B, C, D) 24 | and $N others 25 | note: required by a bound in `App::::service` 26 | --> $WORKSPACE/actix-web/src/app.rs 27 | | 28 | | pub fn service(mut self, factory: F) -> Self 29 | | ------- required by a bound in this associated function 30 | | where 31 | | F: HttpServiceFactory + 'static, 32 | | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::::service` 33 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/route-custom-method.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use actix_web::http::Method; 4 | use actix_web_codegen::route; 5 | 6 | #[route("/single", method = "CUSTOM")] 7 | async fn index() -> String { 8 | "Hello Single!".to_owned() 9 | } 10 | 11 | #[route("/multi", method = "GET", method = "CUSTOM")] 12 | async fn custom() -> String { 13 | "Hello Multi!".to_owned() 14 | } 15 | 16 | #[actix_web::main] 17 | async fn main() { 18 | use actix_web::App; 19 | 20 | let srv = actix_test::start(|| App::new().service(index).service(custom)); 21 | 22 | let request = srv.request(Method::GET, srv.url("/")); 23 | let response = request.send().await.unwrap(); 24 | assert!(response.status().is_client_error()); 25 | 26 | let request = srv.request(Method::from_str("CUSTOM").unwrap(), srv.url("/single")); 27 | let response = request.send().await.unwrap(); 28 | assert!(response.status().is_success()); 29 | 30 | let request = srv.request(Method::GET, srv.url("/multi")); 31 | let response = request.send().await.unwrap(); 32 | assert!(response.status().is_success()); 33 | 34 | let request = srv.request(Method::from_str("CUSTOM").unwrap(), srv.url("/multi")); 35 | let response = request.send().await.unwrap(); 36 | assert!(response.status().is_success()); 37 | } 38 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/route-duplicate-method-fail.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::*; 2 | 3 | #[route("/", method="GET", method="GET")] 4 | async fn index() -> String { 5 | "Hello World!".to_owned() 6 | } 7 | 8 | #[actix_web::main] 9 | async fn main() { 10 | use actix_web::App; 11 | 12 | let srv = actix_test::start(|| App::new().service(index)); 13 | 14 | let request = srv.get("/"); 15 | let response = request.send().await.unwrap(); 16 | assert!(response.status().is_success()); 17 | } 18 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/route-duplicate-method-fail.stderr: -------------------------------------------------------------------------------- 1 | error: HTTP method defined more than once: `GET` 2 | --> tests/trybuild/route-duplicate-method-fail.rs:3:35 3 | | 4 | 3 | #[route("/", method="GET", method="GET")] 5 | | ^^^^^ 6 | 7 | error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied 8 | --> tests/trybuild/route-duplicate-method-fail.rs:12:55 9 | | 10 | 12 | let srv = actix_test::start(|| App::new().service(index)); 11 | | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future {index}` 12 | | | 13 | | required by a bound introduced by this call 14 | | 15 | = help: the following other types implement trait `HttpServiceFactory`: 16 | Resource 17 | actix_web::Scope 18 | Vec 19 | Redirect 20 | (A,) 21 | (A, B) 22 | (A, B, C) 23 | (A, B, C, D) 24 | and $N others 25 | note: required by a bound in `App::::service` 26 | --> $WORKSPACE/actix-web/src/app.rs 27 | | 28 | | pub fn service(mut self, factory: F) -> Self 29 | | ------- required by a bound in this associated function 30 | | where 31 | | F: HttpServiceFactory + 'static, 32 | | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::::service` 33 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/route-malformed-path-fail.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::get; 2 | 3 | #[get("/{")] 4 | async fn zero() -> &'static str { 5 | "malformed resource def" 6 | } 7 | 8 | #[get("/{foo")] 9 | async fn one() -> &'static str { 10 | "malformed resource def" 11 | } 12 | 13 | #[get("/{}")] 14 | async fn two() -> &'static str { 15 | "malformed resource def" 16 | } 17 | 18 | #[get("/*")] 19 | async fn three() -> &'static str { 20 | "malformed resource def" 21 | } 22 | 23 | #[get("/{tail:\\d+}*")] 24 | async fn four() -> &'static str { 25 | "malformed resource def" 26 | } 27 | 28 | #[get("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}")] 29 | async fn five() -> &'static str { 30 | "malformed resource def" 31 | } 32 | 33 | fn main() {} 34 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/route-malformed-path-fail.stderr: -------------------------------------------------------------------------------- 1 | error: custom attribute panicked 2 | --> $DIR/route-malformed-path-fail.rs:3:1 3 | | 4 | 3 | #[get("/{")] 5 | | ^^^^^^^^^^^^ 6 | | 7 | = help: message: pattern "{" contains malformed dynamic segment 8 | 9 | error: custom attribute panicked 10 | --> $DIR/route-malformed-path-fail.rs:8:1 11 | | 12 | 8 | #[get("/{foo")] 13 | | ^^^^^^^^^^^^^^^ 14 | | 15 | = help: message: pattern "{foo" contains malformed dynamic segment 16 | 17 | error: custom attribute panicked 18 | --> $DIR/route-malformed-path-fail.rs:13:1 19 | | 20 | 13 | #[get("/{}")] 21 | | ^^^^^^^^^^^^^ 22 | | 23 | = help: message: Wrong path pattern: "/{}" empty capture group names are not allowed 24 | 25 | error: custom attribute panicked 26 | --> $DIR/route-malformed-path-fail.rs:23:1 27 | | 28 | 23 | #[get("/{tail:\\d+}*")] 29 | | ^^^^^^^^^^^^^^^^^^^^^^^ 30 | | 31 | = help: message: custom regex is not supported for tail match 32 | 33 | error: custom attribute panicked 34 | --> $DIR/route-malformed-path-fail.rs:28:1 35 | | 36 | 28 | #[get("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}")] 37 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 38 | | 39 | = help: message: Only 16 dynamic segments are allowed, provided: 17 40 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/route-missing-method-fail.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::*; 2 | 3 | #[route("/")] 4 | async fn index() -> String { 5 | "Hello World!".to_owned() 6 | } 7 | 8 | #[actix_web::main] 9 | async fn main() { 10 | use actix_web::App; 11 | 12 | let srv = actix_test::start(|| App::new().service(index)); 13 | 14 | let request = srv.get("/"); 15 | let response = request.send().await.unwrap(); 16 | assert!(response.status().is_success()); 17 | } 18 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/route-missing-method-fail.stderr: -------------------------------------------------------------------------------- 1 | error: The #[route(..)] macro requires at least one `method` attribute 2 | --> tests/trybuild/route-missing-method-fail.rs:3:1 3 | | 4 | 3 | #[route("/")] 5 | | ^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `route` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied 10 | --> tests/trybuild/route-missing-method-fail.rs:12:55 11 | | 12 | 12 | let srv = actix_test::start(|| App::new().service(index)); 13 | | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future {index}` 14 | | | 15 | | required by a bound introduced by this call 16 | | 17 | = help: the following other types implement trait `HttpServiceFactory`: 18 | Resource 19 | actix_web::Scope 20 | Vec 21 | Redirect 22 | (A,) 23 | (A, B) 24 | (A, B, C) 25 | (A, B, C, D) 26 | and $N others 27 | note: required by a bound in `App::::service` 28 | --> $WORKSPACE/actix-web/src/app.rs 29 | | 30 | | pub fn service(mut self, factory: F) -> Self 31 | | ------- required by a bound in this associated function 32 | | where 33 | | F: HttpServiceFactory + 'static, 34 | | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::::service` 35 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/route-ok.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::*; 2 | 3 | #[route("/", method="GET", method="HEAD")] 4 | async fn index() -> String { 5 | "Hello World!".to_owned() 6 | } 7 | 8 | #[actix_web::main] 9 | async fn main() { 10 | use actix_web::App; 11 | 12 | let srv = actix_test::start(|| App::new().service(index)); 13 | 14 | let request = srv.get("/"); 15 | let response = request.send().await.unwrap(); 16 | assert!(response.status().is_success()); 17 | } 18 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/routes-missing-args-fail.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::*; 2 | 3 | #[routes] 4 | #[get] 5 | async fn index() -> String { 6 | "Hello World!".to_owned() 7 | } 8 | 9 | #[actix_web::main] 10 | async fn main() { 11 | use actix_web::App; 12 | 13 | let srv = actix_test::start(|| App::new().service(index)); 14 | } 15 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/routes-missing-args-fail.stderr: -------------------------------------------------------------------------------- 1 | error: unexpected end of input, expected string literal 2 | --> tests/trybuild/routes-missing-args-fail.rs:4:1 3 | | 4 | 4 | #[get] 5 | | ^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: invalid service definition, expected #[("")] 10 | --> tests/trybuild/routes-missing-args-fail.rs:4:1 11 | | 12 | 4 | #[get] 13 | | ^^^^^^ 14 | | 15 | = note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info) 16 | 17 | error: expected attribute arguments in parentheses: #[get(...)] 18 | --> tests/trybuild/routes-missing-args-fail.rs:4:3 19 | | 20 | 4 | #[get] 21 | | ^^^ 22 | 23 | error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied 24 | --> tests/trybuild/routes-missing-args-fail.rs:13:55 25 | | 26 | 13 | let srv = actix_test::start(|| App::new().service(index)); 27 | | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future {index}` 28 | | | 29 | | required by a bound introduced by this call 30 | | 31 | = help: the following other types implement trait `HttpServiceFactory`: 32 | Resource 33 | actix_web::Scope 34 | Vec 35 | Redirect 36 | (A,) 37 | (A, B) 38 | (A, B, C) 39 | (A, B, C, D) 40 | and $N others 41 | note: required by a bound in `App::::service` 42 | --> $WORKSPACE/actix-web/src/app.rs 43 | | 44 | | pub fn service(mut self, factory: F) -> Self 45 | | ------- required by a bound in this associated function 46 | | where 47 | | F: HttpServiceFactory + 'static, 48 | | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::::service` 49 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/routes-missing-method-fail.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::*; 2 | 3 | #[routes] 4 | async fn index() -> String { 5 | "Hello World!".to_owned() 6 | } 7 | 8 | #[actix_web::main] 9 | async fn main() { 10 | use actix_web::App; 11 | 12 | let srv = actix_test::start(|| App::new().service(index)); 13 | } 14 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/routes-missing-method-fail.stderr: -------------------------------------------------------------------------------- 1 | error: The #[routes] macro requires at least one `#[(..)]` attribute. 2 | --> tests/trybuild/routes-missing-method-fail.rs:3:1 3 | | 4 | 3 | #[routes] 5 | | ^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `routes` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied 10 | --> tests/trybuild/routes-missing-method-fail.rs:12:55 11 | | 12 | 12 | let srv = actix_test::start(|| App::new().service(index)); 13 | | ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future {index}` 14 | | | 15 | | required by a bound introduced by this call 16 | | 17 | = help: the following other types implement trait `HttpServiceFactory`: 18 | Resource 19 | actix_web::Scope 20 | Vec 21 | Redirect 22 | (A,) 23 | (A, B) 24 | (A, B, C) 25 | (A, B, C, D) 26 | and $N others 27 | note: required by a bound in `App::::service` 28 | --> $WORKSPACE/actix-web/src/app.rs 29 | | 30 | | pub fn service(mut self, factory: F) -> Self 31 | | ------- required by a bound in this associated function 32 | | where 33 | | F: HttpServiceFactory + 'static, 34 | | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::::service` 35 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/routes-ok.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::*; 2 | 3 | #[routes] 4 | #[get("/")] 5 | #[post("/")] 6 | async fn index() -> String { 7 | "Hello World!".to_owned() 8 | } 9 | 10 | #[actix_web::main] 11 | async fn main() { 12 | use actix_web::App; 13 | 14 | let srv = actix_test::start(|| App::new().service(index)); 15 | 16 | let request = srv.get("/"); 17 | let response = request.send().await.unwrap(); 18 | assert!(response.status().is_success()); 19 | 20 | let request = srv.post("/"); 21 | let response = request.send().await.unwrap(); 22 | assert!(response.status().is_success()); 23 | } 24 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/scope-invalid-args.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::scope; 2 | 3 | const PATH: &str = "/api"; 4 | 5 | #[scope(PATH)] 6 | mod api_const {} 7 | 8 | #[scope(true)] 9 | mod api_bool {} 10 | 11 | #[scope(123)] 12 | mod api_num {} 13 | 14 | fn main() {} 15 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/scope-invalid-args.stderr: -------------------------------------------------------------------------------- 1 | error: argument to scope macro is not a string literal, expected: #[scope("/prefix")] 2 | --> tests/trybuild/scope-invalid-args.rs:5:9 3 | | 4 | 5 | #[scope(PATH)] 5 | | ^^^^ 6 | 7 | error: argument to scope macro is not a string literal, expected: #[scope("/prefix")] 8 | --> tests/trybuild/scope-invalid-args.rs:8:9 9 | | 10 | 8 | #[scope(true)] 11 | | ^^^^ 12 | 13 | error: argument to scope macro is not a string literal, expected: #[scope("/prefix")] 14 | --> tests/trybuild/scope-invalid-args.rs:11:9 15 | | 16 | 11 | #[scope(123)] 17 | | ^^^ 18 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/scope-missing-args.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::scope; 2 | 3 | #[scope] 4 | mod api {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/scope-missing-args.stderr: -------------------------------------------------------------------------------- 1 | error: missing arguments for scope macro, expected: #[scope("/prefix")] 2 | --> tests/trybuild/scope-missing-args.rs:3:1 3 | | 4 | 3 | #[scope] 5 | | ^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `scope` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/scope-on-handler.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::scope; 2 | 3 | #[scope("/api")] 4 | async fn index() -> &'static str { 5 | "Hello World!" 6 | } 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/scope-on-handler.stderr: -------------------------------------------------------------------------------- 1 | error: #[scope] macro must be attached to a module 2 | --> tests/trybuild/scope-on-handler.rs:4:1 3 | | 4 | 4 | async fn index() -> &'static str { 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/scope-trailing-slash.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::scope; 2 | 3 | #[scope("/api/")] 4 | mod api {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/scope-trailing-slash.stderr: -------------------------------------------------------------------------------- 1 | error: scopes should not have trailing slashes; see https://docs.rs/actix-web/4/actix_web/struct.Scope.html#avoid-trailing-slashes 2 | --> tests/trybuild/scope-trailing-slash.rs:3:9 3 | | 4 | 3 | #[scope("/api/")] 5 | | ^^^^^^^ 6 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/simple-fail.rs: -------------------------------------------------------------------------------- 1 | use actix_web_codegen::*; 2 | 3 | #[get("/one", other)] 4 | async fn one() -> String { 5 | "Hello World!".to_owned() 6 | } 7 | 8 | #[post(/two)] 9 | async fn two() -> String { 10 | "Hello World!".to_owned() 11 | } 12 | 13 | static PATCH_PATH: &str = "/three"; 14 | 15 | #[patch(PATCH_PATH)] 16 | async fn three() -> String { 17 | "Hello World!".to_owned() 18 | } 19 | 20 | #[delete("/four", "/five")] 21 | async fn four() -> String { 22 | "Hello World!".to_owned() 23 | } 24 | 25 | #[delete("/five", method="GET")] 26 | async fn five() -> String { 27 | "Hello World!".to_owned() 28 | } 29 | 30 | fn main() {} 31 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/simple-fail.stderr: -------------------------------------------------------------------------------- 1 | error: expected `=` 2 | --> $DIR/simple-fail.rs:3:1 3 | | 4 | 3 | #[get("/one", other)] 5 | | ^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: expected string literal 10 | --> $DIR/simple-fail.rs:8:8 11 | | 12 | 8 | #[post(/two)] 13 | | ^ 14 | 15 | error: invalid service definition, expected #[("")] 16 | --> $DIR/simple-fail.rs:8:8 17 | | 18 | 8 | #[post(/two)] 19 | | ^ 20 | 21 | error: expected string literal 22 | --> $DIR/simple-fail.rs:15:9 23 | | 24 | 15 | #[patch(PATCH_PATH)] 25 | | ^^^^^^^^^^ 26 | 27 | error: invalid service definition, expected #[("")] 28 | --> $DIR/simple-fail.rs:15:9 29 | | 30 | 15 | #[patch(PATCH_PATH)] 31 | | ^^^^^^^^^^ 32 | 33 | error: Multiple paths specified! There should be only one. 34 | --> $DIR/simple-fail.rs:20:1 35 | | 36 | 20 | #[delete("/four", "/five")] 37 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 38 | | 39 | = note: this error originates in the attribute macro `delete` (in Nightly builds, run with -Z macro-backtrace for more info) 40 | 41 | error: HTTP method forbidden here; to handle multiple methods, use `route` instead 42 | --> $DIR/simple-fail.rs:25:19 43 | | 44 | 25 | #[delete("/five", method="GET")] 45 | | ^^^^^^^^^^^^ 46 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/simple.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{Responder, HttpResponse, App}; 2 | use actix_web_codegen::*; 3 | 4 | #[get("/config")] 5 | async fn config() -> impl Responder { 6 | HttpResponse::Ok() 7 | } 8 | 9 | #[actix_web::main] 10 | async fn main() { 11 | let srv = actix_test::start(|| App::new().service(config)); 12 | 13 | let request = srv.get("/config"); 14 | let response = request.send().await.unwrap(); 15 | assert!(response.status().is_success()); 16 | } 17 | -------------------------------------------------------------------------------- /actix-web-codegen/tests/trybuild/test-runtime.rs: -------------------------------------------------------------------------------- 1 | #[actix_web::test] 2 | async fn my_test() { 3 | assert!(async { 1 }.await, 1); 4 | } 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /actix-web/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /actix-web/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /actix-web/MIGRATION-2.0.md: -------------------------------------------------------------------------------- 1 | # Migrating to 2.0.0 2 | 3 | - `HttpServer::start()` renamed to `HttpServer::run()`. It also possible to `.await` on `run` method result, in that case it awaits server exit. 4 | 5 | - `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. 6 | 7 | - Extractor configuration must be registered with `App::app_data()` instead of `App::data()` 8 | 9 | - Sync handlers has been removed. `.to_async()` method has been renamed to `.to()` replace `fn` with `async fn` to convert sync handler to async 10 | 11 | - `actix_http_test::TestServer` moved to `actix_web::test` module. To start test server use `test::start()` or `test_start_with_config()` methods 12 | 13 | - `ResponseError` trait has been refactored. `ResponseError::error_response()` renders http response. 14 | 15 | - Feature `rust-tls` renamed to `rustls` 16 | 17 | instead of 18 | 19 | ```rust 20 | actix-web = { version = "2.0.0", features = ["rust-tls"] } 21 | ``` 22 | 23 | use 24 | 25 | ```rust 26 | actix-web = { version = "2.0.0", features = ["rustls"] } 27 | ``` 28 | 29 | - Feature `ssl` renamed to `openssl` 30 | 31 | instead of 32 | 33 | ```rust 34 | actix-web = { version = "2.0.0", features = ["ssl"] } 35 | ``` 36 | 37 | use 38 | 39 | ```rust 40 | actix-web = { version = "2.0.0", features = ["openssl"] } 41 | ``` 42 | 43 | - `Cors` builder now requires that you call `.finish()` to construct the middleware 44 | -------------------------------------------------------------------------------- /actix-web/MIGRATION-3.0.md: -------------------------------------------------------------------------------- 1 | # Migrating to 3.0.0 2 | 3 | - The return type for `ServiceRequest::app_data::()` was changed from returning a `Data` to simply a `T`. To access a `Data` use `ServiceRequest::app_data::>()`. 4 | 5 | - Cookie handling has been offloaded to the `cookie` crate: 6 | 7 | - `USERINFO_ENCODE_SET` is no longer exposed. Percent-encoding is still supported; check docs. 8 | - Some types now require lifetime parameters. 9 | 10 | - The time crate was updated to `v0.2`, a major breaking change to the time crate, which affects any `actix-web` method previously expecting a time v0.1 input. 11 | 12 | - 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. 13 | 14 | - actix-http support for Actors messages was moved to actix-http crate and is enabled with feature `actors` 15 | 16 | - content_length function is removed from actix-http. You can set Content-Length by normally setting the response body or calling no_chunking function. 17 | 18 | - `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a `u64` instead of a `usize`. 19 | 20 | - Code that was using `path.` to access a `web::Path<(A, B, C)>`s elements now needs to use destructuring or `.into_inner()`. For example: 21 | 22 | ```rust 23 | // Previously: 24 | async fn some_route(path: web::Path<(String, String)>) -> String { 25 | format!("Hello, {} {}", path.0, path.1) 26 | } 27 | 28 | // Now (this also worked before): 29 | async fn some_route(path: web::Path<(String, String)>) -> String { 30 | let (first_name, last_name) = path.into_inner(); 31 | format!("Hello, {} {}", first_name, last_name) 32 | } 33 | // Or (this wasn't previously supported): 34 | async fn some_route(web::Path((first_name, last_name)): web::Path<(String, String)>) -> String { 35 | format!("Hello, {} {}", first_name, last_name) 36 | } 37 | ``` 38 | 39 | - `middleware::NormalizePath` can now also be configured to trim trailing slashes instead of always keeping one. It will need `middleware::normalize::TrailingSlash` when being constructed with `NormalizePath::new(...)`, or for an easier migration you can replace `wrap(middleware::NormalizePath)` with `wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))`. 40 | 41 | - `HttpServer::maxconn` is renamed to the more expressive `HttpServer::max_connections`. 42 | 43 | - `HttpServer::maxconnrate` is renamed to the more expressive `HttpServer::max_connection_rate`. 44 | -------------------------------------------------------------------------------- /actix-web/examples/README.md: -------------------------------------------------------------------------------- 1 | # Actix Web Examples 2 | 3 | This folder contain just a few standalone code samples. There is a much larger registry of example projects [in the examples repo](https://github.com/actix/examples). 4 | -------------------------------------------------------------------------------- /actix-web/examples/basic.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{get, middleware, web, App, HttpRequest, HttpResponse, HttpServer}; 2 | 3 | #[get("/resource1/{name}/index.html")] 4 | async fn index(req: HttpRequest, name: web::Path) -> String { 5 | println!("REQ: {:?}", req); 6 | format!("Hello: {}!\r\n", name) 7 | } 8 | 9 | async fn index_async(req: HttpRequest) -> &'static str { 10 | println!("REQ: {:?}", req); 11 | "Hello world!\r\n" 12 | } 13 | 14 | #[get("/")] 15 | async fn no_params() -> &'static str { 16 | "Hello world!\r\n" 17 | } 18 | 19 | #[actix_web::main] 20 | async fn main() -> std::io::Result<()> { 21 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 22 | 23 | log::info!("starting HTTP server at http://localhost:8080"); 24 | 25 | HttpServer::new(|| { 26 | App::new() 27 | .wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2"))) 28 | .wrap(middleware::Compress::default()) 29 | .wrap(middleware::Logger::default().log_target("http_log")) 30 | .service(index) 31 | .service(no_params) 32 | .service( 33 | web::resource("/resource2/index.html") 34 | .wrap(middleware::DefaultHeaders::new().add(("X-Version-R2", "0.3"))) 35 | .default_service(web::route().to(HttpResponse::MethodNotAllowed)) 36 | .route(web::get().to(index_async)), 37 | ) 38 | .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) 39 | }) 40 | .bind(("127.0.0.1", 8080))? 41 | .workers(1) 42 | .run() 43 | .await 44 | } 45 | -------------------------------------------------------------------------------- /actix-web/examples/macroless.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{middleware, rt, web, App, HttpRequest, HttpServer}; 2 | 3 | async fn index(req: HttpRequest) -> &'static str { 4 | println!("REQ: {:?}", req); 5 | "Hello world!\r\n" 6 | } 7 | 8 | fn main() -> std::io::Result<()> { 9 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 10 | 11 | rt::System::new().block_on( 12 | HttpServer::new(|| { 13 | App::new() 14 | .wrap(middleware::Logger::default()) 15 | .service(web::resource("/").route(web::get().to(index))) 16 | }) 17 | .bind(("127.0.0.1", 8080))? 18 | .workers(1) 19 | .run(), 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /actix-web/examples/on-connect.rs: -------------------------------------------------------------------------------- 1 | //! This example shows how to use `actix_web::HttpServer::on_connect` to access a lower-level socket 2 | //! properties and pass them to a handler through request-local data. 3 | //! 4 | //! For an example of extracting a client TLS certificate, see: 5 | //! 6 | 7 | use std::{any::Any, io, net::SocketAddr}; 8 | 9 | use actix_web::{ 10 | dev::Extensions, rt::net::TcpStream, web, App, HttpRequest, HttpResponse, HttpServer, Responder, 11 | }; 12 | 13 | #[allow(dead_code)] 14 | #[derive(Debug, Clone)] 15 | struct ConnectionInfo { 16 | bind: SocketAddr, 17 | peer: SocketAddr, 18 | ttl: Option, 19 | } 20 | 21 | async fn route_whoami(req: HttpRequest) -> impl Responder { 22 | match req.conn_data::() { 23 | Some(info) => HttpResponse::Ok().body(format!( 24 | "Here is some info about your connection:\n\n{info:#?}", 25 | )), 26 | None => HttpResponse::InternalServerError().body("Missing expected request extension data"), 27 | } 28 | } 29 | 30 | fn get_conn_info(connection: &dyn Any, data: &mut Extensions) { 31 | if let Some(sock) = connection.downcast_ref::() { 32 | data.insert(ConnectionInfo { 33 | bind: sock.local_addr().unwrap(), 34 | peer: sock.peer_addr().unwrap(), 35 | ttl: sock.ttl().ok(), 36 | }); 37 | } else { 38 | unreachable!("connection should only be plaintext since no TLS is set up"); 39 | } 40 | } 41 | 42 | #[actix_web::main] 43 | async fn main() -> io::Result<()> { 44 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 45 | 46 | let bind = ("127.0.0.1", 8080); 47 | log::info!("staring server at http://{}:{}", &bind.0, &bind.1); 48 | 49 | HttpServer::new(|| App::new().default_service(web::to(route_whoami))) 50 | .on_connect(get_conn_info) 51 | .bind_auto_h2c(bind)? 52 | .workers(2) 53 | .run() 54 | .await 55 | } 56 | -------------------------------------------------------------------------------- /actix-web/examples/uds.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{get, web, HttpRequest}; 2 | #[cfg(unix)] 3 | use actix_web::{middleware, App, Error, HttpResponse, HttpServer}; 4 | 5 | #[get("/resource1/{name}/index.html")] 6 | async fn index(req: HttpRequest, name: web::Path) -> String { 7 | println!("REQ: {:?}", req); 8 | format!("Hello: {}!\r\n", name) 9 | } 10 | 11 | #[cfg(unix)] 12 | async fn index_async(req: HttpRequest) -> Result<&'static str, Error> { 13 | println!("REQ: {:?}", req); 14 | Ok("Hello world!\r\n") 15 | } 16 | 17 | #[get("/")] 18 | async fn no_params() -> &'static str { 19 | "Hello world!\r\n" 20 | } 21 | 22 | #[cfg(unix)] 23 | #[actix_web::main] 24 | async fn main() -> std::io::Result<()> { 25 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 26 | 27 | HttpServer::new(|| { 28 | App::new() 29 | .wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2"))) 30 | .wrap(middleware::Compress::default()) 31 | .wrap(middleware::Logger::default()) 32 | .service(index) 33 | .service(no_params) 34 | .service( 35 | web::resource("/resource2/index.html") 36 | .wrap(middleware::DefaultHeaders::new().add(("X-Version-R2", "0.3"))) 37 | .default_service(web::route().to(HttpResponse::MethodNotAllowed)) 38 | .route(web::get().to(index_async)), 39 | ) 40 | .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) 41 | }) 42 | .bind_uds("/Users/me/uds-test")? 43 | .workers(1) 44 | .run() 45 | .await 46 | } 47 | 48 | #[cfg(not(unix))] 49 | fn main() {} 50 | -------------------------------------------------------------------------------- /actix-web/examples/worker-cpu-pin.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | sync::{ 4 | atomic::{AtomicUsize, Ordering}, 5 | Arc, 6 | }, 7 | thread, 8 | }; 9 | 10 | use actix_web::{middleware, web, App, HttpServer}; 11 | 12 | async fn hello() -> &'static str { 13 | "Hello world!" 14 | } 15 | 16 | #[actix_web::main] 17 | async fn main() -> io::Result<()> { 18 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 19 | 20 | let core_ids = core_affinity::get_core_ids().unwrap(); 21 | let n_core_ids = core_ids.len(); 22 | let next_core_id = Arc::new(AtomicUsize::new(0)); 23 | 24 | HttpServer::new(move || { 25 | let pin = Arc::clone(&next_core_id).fetch_add(1, Ordering::AcqRel); 26 | log::info!( 27 | "setting CPU affinity for worker {}: pinning to core {}", 28 | thread::current().name().unwrap(), 29 | pin, 30 | ); 31 | core_affinity::set_for_current(core_ids[pin]); 32 | 33 | App::new() 34 | .wrap(middleware::Logger::default()) 35 | .service(web::resource("/").get(hello)) 36 | }) 37 | .bind(("127.0.0.1", 8080))? 38 | .workers(n_core_ids) 39 | .run() 40 | .await 41 | } 42 | -------------------------------------------------------------------------------- /actix-web/src/dev.rs: -------------------------------------------------------------------------------- 1 | //! Lower-level types and re-exports. 2 | //! 3 | //! Most users will not have to interact with the types in this module, but it is useful for those 4 | //! writing extractors, middleware, libraries, or interacting with the service API directly. 5 | //! 6 | //! # Request Extractors 7 | //! - [`ConnectionInfo`]: Connection information 8 | //! - [`PeerAddr`]: Connection information 9 | 10 | #[cfg(feature = "__compress")] 11 | pub use actix_http::encoding::Decoder as Decompress; 12 | pub use actix_http::{Extensions, Payload, RequestHead, Response, ResponseHead}; 13 | use actix_router::Patterns; 14 | pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; 15 | pub use actix_server::{Server, ServerHandle}; 16 | pub use actix_service::{ 17 | always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform, 18 | }; 19 | 20 | #[doc(hidden)] 21 | pub use crate::handler::Handler; 22 | pub use crate::{ 23 | config::{AppConfig, AppService}, 24 | info::{ConnectionInfo, PeerAddr}, 25 | rmap::ResourceMap, 26 | service::{HttpServiceFactory, ServiceRequest, ServiceResponse, WebService}, 27 | types::{JsonBody, Readlines, UrlEncoded}, 28 | }; 29 | 30 | pub(crate) fn ensure_leading_slash(mut patterns: Patterns) -> Patterns { 31 | match &mut patterns { 32 | Patterns::Single(pat) => { 33 | if !pat.is_empty() && !pat.starts_with('/') { 34 | pat.insert(0, '/'); 35 | }; 36 | } 37 | Patterns::List(pats) => { 38 | for pat in pats { 39 | if !pat.is_empty() && !pat.starts_with('/') { 40 | pat.insert(0, '/'); 41 | }; 42 | } 43 | } 44 | } 45 | 46 | patterns 47 | } 48 | -------------------------------------------------------------------------------- /actix-web/src/error/error.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error as StdError, fmt}; 2 | 3 | use actix_http::{body::BoxBody, Response}; 4 | 5 | use crate::{HttpResponse, ResponseError}; 6 | 7 | /// General purpose Actix Web error. 8 | /// 9 | /// An Actix Web error is used to carry errors from `std::error` through actix in a convenient way. 10 | /// It can be created through converting errors with `into()`. 11 | /// 12 | /// Whenever it is created from an external object a response error is created for it that can be 13 | /// used to create an HTTP response from it this means that if you have access to an actix `Error` 14 | /// you can always get a `ResponseError` reference from it. 15 | pub struct Error { 16 | cause: Box, 17 | } 18 | 19 | impl Error { 20 | /// Returns the reference to the underlying `ResponseError`. 21 | pub fn as_response_error(&self) -> &dyn ResponseError { 22 | self.cause.as_ref() 23 | } 24 | 25 | /// Similar to `as_response_error` but downcasts. 26 | pub fn as_error(&self) -> Option<&T> { 27 | ::downcast_ref(self.cause.as_ref()) 28 | } 29 | 30 | /// Shortcut for creating an `HttpResponse`. 31 | pub fn error_response(&self) -> HttpResponse { 32 | self.cause.error_response() 33 | } 34 | } 35 | 36 | impl fmt::Display for Error { 37 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 38 | fmt::Display::fmt(&self.cause, f) 39 | } 40 | } 41 | 42 | impl fmt::Debug for Error { 43 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 | write!(f, "{:?}", &self.cause) 45 | } 46 | } 47 | 48 | impl StdError for Error { 49 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 50 | None 51 | } 52 | } 53 | 54 | /// `Error` for any error that implements `ResponseError` 55 | impl From for Error { 56 | fn from(err: T) -> Error { 57 | Error { 58 | cause: Box::new(err), 59 | } 60 | } 61 | } 62 | 63 | impl From> for Error { 64 | fn from(value: Box) -> Self { 65 | Error { cause: value } 66 | } 67 | } 68 | 69 | impl From for Response { 70 | fn from(err: Error) -> Response { 71 | err.error_response().into() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /actix-web/src/helpers.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use bytes::BufMut; 4 | 5 | /// An `io::Write`r that only requires mutable reference and assumes that there is space available 6 | /// in the buffer for every write operation or that it can be extended implicitly (like 7 | /// `bytes::BytesMut`, for example). 8 | /// 9 | /// This is slightly faster (~10%) than `bytes::buf::Writer` in such cases because it does not 10 | /// perform a remaining length check before writing. 11 | pub(crate) struct MutWriter<'a, B>(pub(crate) &'a mut B); 12 | 13 | impl io::Write for MutWriter<'_, B> 14 | where 15 | B: BufMut, 16 | { 17 | fn write(&mut self, buf: &[u8]) -> io::Result { 18 | self.0.put_slice(buf); 19 | Ok(buf.len()) 20 | } 21 | 22 | fn flush(&mut self) -> io::Result<()> { 23 | Ok(()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /actix-web/src/http/header/accept_charset.rs: -------------------------------------------------------------------------------- 1 | use super::{common_header, Charset, QualityItem, ACCEPT_CHARSET}; 2 | 3 | common_header! { 4 | /// `Accept-Charset` header, defined in [RFC 7231 §5.3.3]. 5 | /// 6 | /// The `Accept-Charset` header field can be sent by a user agent to 7 | /// indicate what charsets are acceptable in textual response content. 8 | /// This field allows user agents capable of understanding more 9 | /// comprehensive or special-purpose charsets to signal that capability 10 | /// to an origin server that is capable of representing information in 11 | /// those charsets. 12 | /// 13 | /// # ABNF 14 | /// ```plain 15 | /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) 16 | /// ``` 17 | /// 18 | /// # Example Values 19 | /// * `iso-8859-5, unicode-1-1;q=0.8` 20 | /// 21 | /// # Examples 22 | /// ``` 23 | /// use actix_web::HttpResponse; 24 | /// use actix_web::http::header::{AcceptCharset, Charset, QualityItem}; 25 | /// 26 | /// let mut builder = HttpResponse::Ok(); 27 | /// builder.insert_header( 28 | /// AcceptCharset(vec![QualityItem::max(Charset::Us_Ascii)]) 29 | /// ); 30 | /// ``` 31 | /// 32 | /// ``` 33 | /// use actix_web::HttpResponse; 34 | /// use actix_web::http::header::{AcceptCharset, Charset, q, QualityItem}; 35 | /// 36 | /// let mut builder = HttpResponse::Ok(); 37 | /// builder.insert_header( 38 | /// AcceptCharset(vec![ 39 | /// QualityItem::new(Charset::Us_Ascii, q(0.9)), 40 | /// QualityItem::new(Charset::Iso_8859_10, q(0.2)), 41 | /// ]) 42 | /// ); 43 | /// ``` 44 | /// 45 | /// ``` 46 | /// use actix_web::HttpResponse; 47 | /// use actix_web::http::header::{AcceptCharset, Charset, QualityItem}; 48 | /// 49 | /// let mut builder = HttpResponse::Ok(); 50 | /// builder.insert_header( 51 | /// AcceptCharset(vec![QualityItem::max(Charset::Ext("utf-8".to_owned()))]) 52 | /// ); 53 | /// ``` 54 | /// 55 | /// [RFC 7231 §5.3.3]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.3 56 | (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)* 57 | 58 | test_parse_and_format { 59 | // Test case from RFC 60 | common_header_test!(test1, [b"iso-8859-5, unicode-1-1;q=0.8"]); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /actix-web/src/http/header/allow.rs: -------------------------------------------------------------------------------- 1 | use actix_http::Method; 2 | 3 | use crate::http::header; 4 | 5 | crate::http::header::common_header! { 6 | /// `Allow` header, defined 7 | /// in [RFC 7231 §7.4.1](https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1) 8 | /// 9 | /// The `Allow` header field lists the set of methods advertised as 10 | /// supported by the target resource. The purpose of this field is 11 | /// strictly to inform the recipient of valid request methods associated 12 | /// with the resource. 13 | /// 14 | /// # ABNF 15 | /// ```plain 16 | /// Allow = #method 17 | /// ``` 18 | /// 19 | /// # Example Values 20 | /// * `GET, HEAD, PUT` 21 | /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` 22 | /// * `` 23 | /// 24 | /// # Examples 25 | /// ``` 26 | /// use actix_web::HttpResponse; 27 | /// use actix_web::http::{header::Allow, Method}; 28 | /// 29 | /// let mut builder = HttpResponse::Ok(); 30 | /// builder.insert_header( 31 | /// Allow(vec![Method::GET]) 32 | /// ); 33 | /// ``` 34 | /// 35 | /// ``` 36 | /// use actix_web::HttpResponse; 37 | /// use actix_web::http::{header::Allow, Method}; 38 | /// 39 | /// let mut builder = HttpResponse::Ok(); 40 | /// builder.insert_header( 41 | /// Allow(vec![ 42 | /// Method::GET, 43 | /// Method::POST, 44 | /// Method::PATCH, 45 | /// ]) 46 | /// ); 47 | /// ``` 48 | (Allow, header::ALLOW) => (Method)* 49 | 50 | test_parse_and_format { 51 | // from the RFC 52 | 53 | crate::http::header::common_header_test!( 54 | test1, 55 | [b"GET, HEAD, PUT"], 56 | Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); 57 | 58 | // other tests 59 | 60 | crate::http::header::common_header_test!( 61 | test2, 62 | [b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], 63 | Some(HeaderField(vec![ 64 | Method::OPTIONS, 65 | Method::GET, 66 | Method::PUT, 67 | Method::POST, 68 | Method::DELETE, 69 | Method::HEAD, 70 | Method::TRACE, 71 | Method::CONNECT, 72 | Method::PATCH]))); 73 | 74 | crate::http::header::common_header_test!( 75 | test3, 76 | [b""], 77 | Some(HeaderField(Vec::::new()))); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /actix-web/src/http/header/any_or_some.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{self, Write as _}, 3 | str, 4 | }; 5 | 6 | /// A wrapper for types used in header values where wildcard (`*`) items are allowed but the 7 | /// underlying type does not support them. 8 | /// 9 | /// For example, we use the `language-tags` crate for the [`AcceptLanguage`](super::AcceptLanguage) 10 | /// typed header but it does parse `*` successfully. On the other hand, the `mime` crate, used for 11 | /// [`Accept`](super::Accept), has first-party support for wildcard items so this wrapper is not 12 | /// used in those header types. 13 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Hash)] 14 | pub enum AnyOrSome { 15 | /// A wildcard value. 16 | Any, 17 | 18 | /// A valid `T`. 19 | Item(T), 20 | } 21 | 22 | impl AnyOrSome { 23 | /// Returns true if item is wildcard (`*`) variant. 24 | pub fn is_any(&self) -> bool { 25 | matches!(self, Self::Any) 26 | } 27 | 28 | /// Returns true if item is a valid item (`T`) variant. 29 | pub fn is_item(&self) -> bool { 30 | matches!(self, Self::Item(_)) 31 | } 32 | 33 | /// Returns reference to value in `Item` variant, if it is set. 34 | pub fn item(&self) -> Option<&T> { 35 | match self { 36 | AnyOrSome::Item(ref item) => Some(item), 37 | AnyOrSome::Any => None, 38 | } 39 | } 40 | 41 | /// Consumes the container, returning the value in the `Item` variant, if it is set. 42 | pub fn into_item(self) -> Option { 43 | match self { 44 | AnyOrSome::Item(item) => Some(item), 45 | AnyOrSome::Any => None, 46 | } 47 | } 48 | } 49 | 50 | impl fmt::Display for AnyOrSome { 51 | #[inline] 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | match self { 54 | AnyOrSome::Any => f.write_char('*'), 55 | AnyOrSome::Item(item) => fmt::Display::fmt(item, f), 56 | } 57 | } 58 | } 59 | 60 | impl str::FromStr for AnyOrSome { 61 | type Err = T::Err; 62 | 63 | #[inline] 64 | fn from_str(s: &str) -> Result { 65 | match s.trim() { 66 | "*" => Ok(Self::Any), 67 | other => other.parse().map(AnyOrSome::Item), 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /actix-web/src/http/header/content_language.rs: -------------------------------------------------------------------------------- 1 | use language_tags::LanguageTag; 2 | 3 | use super::{common_header, QualityItem, CONTENT_LANGUAGE}; 4 | 5 | common_header! { 6 | /// `Content-Language` header, defined 7 | /// in [RFC 7231 §3.1.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.3.2) 8 | /// 9 | /// The `Content-Language` header field describes the natural language(s) 10 | /// of the intended audience for the representation. Note that this 11 | /// might not be equivalent to all the languages used within the 12 | /// representation. 13 | /// 14 | /// # ABNF 15 | /// ```plain 16 | /// Content-Language = 1#language-tag 17 | /// ``` 18 | /// 19 | /// # Example Values 20 | /// * `da` 21 | /// * `mi, en` 22 | /// 23 | /// # Examples 24 | /// ``` 25 | /// use actix_web::HttpResponse; 26 | /// use actix_web::http::header::{ContentLanguage, LanguageTag, QualityItem}; 27 | /// 28 | /// let mut builder = HttpResponse::Ok(); 29 | /// builder.insert_header( 30 | /// ContentLanguage(vec![ 31 | /// QualityItem::max(LanguageTag::parse("en").unwrap()), 32 | /// ]) 33 | /// ); 34 | /// ``` 35 | /// 36 | /// ``` 37 | /// use actix_web::HttpResponse; 38 | /// use actix_web::http::header::{ContentLanguage, LanguageTag, QualityItem}; 39 | /// 40 | /// let mut builder = HttpResponse::Ok(); 41 | /// builder.insert_header( 42 | /// ContentLanguage(vec![ 43 | /// QualityItem::max(LanguageTag::parse("da").unwrap()), 44 | /// QualityItem::max(LanguageTag::parse("en-GB").unwrap()), 45 | /// ]) 46 | /// ); 47 | /// ``` 48 | (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ 49 | 50 | test_parse_and_format { 51 | crate::http::header::common_header_test!(test1, [b"da"]); 52 | crate::http::header::common_header_test!(test2, [b"mi, en"]); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /actix-web/src/http/header/date.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | 3 | use super::{HttpDate, DATE}; 4 | 5 | crate::http::header::common_header! { 6 | /// `Date` header, defined 7 | /// in [RFC 7231 §7.1.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2) 8 | /// 9 | /// The `Date` header field represents the date and time at which the 10 | /// message was originated. 11 | /// 12 | /// # ABNF 13 | /// ```plain 14 | /// Date = HTTP-date 15 | /// ``` 16 | /// 17 | /// # Example Values 18 | /// * `Tue, 15 Nov 1994 08:12:31 GMT` 19 | /// 20 | /// # Examples 21 | /// 22 | /// ``` 23 | /// use std::time::SystemTime; 24 | /// use actix_web::HttpResponse; 25 | /// use actix_web::http::header::Date; 26 | /// 27 | /// let mut builder = HttpResponse::Ok(); 28 | /// builder.insert_header( 29 | /// Date(SystemTime::now().into()) 30 | /// ); 31 | /// ``` 32 | (Date, DATE) => [HttpDate] 33 | 34 | test_parse_and_format { 35 | crate::http::header::common_header_test!(test1, [b"Tue, 15 Nov 1994 08:12:31 GMT"]); 36 | } 37 | } 38 | 39 | impl Date { 40 | /// Create a date instance set to the current system time 41 | pub fn now() -> Date { 42 | Date(SystemTime::now().into()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /actix-web/src/http/header/encoding.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, str}; 2 | 3 | use actix_http::ContentEncoding; 4 | 5 | /// A value to represent an encoding used in the `Accept-Encoding` and `Content-Encoding` header. 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 7 | pub enum Encoding { 8 | /// A supported content encoding. See [`ContentEncoding`] for variants. 9 | Known(ContentEncoding), 10 | 11 | /// Some other encoding that is less common, can be any string. 12 | Unknown(String), 13 | } 14 | 15 | impl Encoding { 16 | pub const fn identity() -> Self { 17 | Self::Known(ContentEncoding::Identity) 18 | } 19 | 20 | pub const fn brotli() -> Self { 21 | Self::Known(ContentEncoding::Brotli) 22 | } 23 | 24 | pub const fn deflate() -> Self { 25 | Self::Known(ContentEncoding::Deflate) 26 | } 27 | 28 | pub const fn gzip() -> Self { 29 | Self::Known(ContentEncoding::Gzip) 30 | } 31 | 32 | pub const fn zstd() -> Self { 33 | Self::Known(ContentEncoding::Zstd) 34 | } 35 | } 36 | 37 | impl fmt::Display for Encoding { 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 39 | f.write_str(match self { 40 | Encoding::Known(enc) => enc.as_str(), 41 | Encoding::Unknown(enc) => enc.as_str(), 42 | }) 43 | } 44 | } 45 | 46 | impl str::FromStr for Encoding { 47 | type Err = crate::error::ParseError; 48 | 49 | fn from_str(enc: &str) -> Result { 50 | match enc.parse::() { 51 | Ok(enc) => Ok(Self::Known(enc)), 52 | Err(_) => Ok(Self::Unknown(enc.to_owned())), 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /actix-web/src/http/header/expires.rs: -------------------------------------------------------------------------------- 1 | use super::{HttpDate, EXPIRES}; 2 | 3 | crate::http::header::common_header! { 4 | /// `Expires` header, defined 5 | /// in [RFC 7234 §5.3](https://datatracker.ietf.org/doc/html/rfc7234#section-5.3) 6 | /// 7 | /// The `Expires` header field gives the date/time after which the 8 | /// response is considered stale. 9 | /// 10 | /// The presence of an Expires field does not imply that the original 11 | /// resource will change or cease to exist at, before, or after that 12 | /// time. 13 | /// 14 | /// # ABNF 15 | /// ```plain 16 | /// Expires = HTTP-date 17 | /// ``` 18 | /// 19 | /// # Example Values 20 | /// * `Thu, 01 Dec 1994 16:00:00 GMT` 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// use std::time::{SystemTime, Duration}; 26 | /// use actix_web::HttpResponse; 27 | /// use actix_web::http::header::Expires; 28 | /// 29 | /// let mut builder = HttpResponse::Ok(); 30 | /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); 31 | /// builder.insert_header( 32 | /// Expires(expiration.into()) 33 | /// ); 34 | /// ``` 35 | (Expires, EXPIRES) => [HttpDate] 36 | 37 | test_parse_and_format { 38 | // Test case from RFC 39 | crate::http::header::common_header_test!(test1, [b"Thu, 01 Dec 1994 16:00:00 GMT"]); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /actix-web/src/http/header/if_modified_since.rs: -------------------------------------------------------------------------------- 1 | use super::{HttpDate, IF_MODIFIED_SINCE}; 2 | 3 | crate::http::header::common_header! { 4 | /// `If-Modified-Since` header, defined 5 | /// in [RFC 7232 §3.3](https://datatracker.ietf.org/doc/html/rfc7232#section-3.3) 6 | /// 7 | /// The `If-Modified-Since` header field makes a GET or HEAD request 8 | /// method conditional on the selected representation's modification date 9 | /// being more recent than the date provided in the field-value. 10 | /// Transfer of the selected representation's data is avoided if that 11 | /// data has not changed. 12 | /// 13 | /// # ABNF 14 | /// ```plain 15 | /// If-Unmodified-Since = HTTP-date 16 | /// ``` 17 | /// 18 | /// # Example Values 19 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 20 | /// 21 | /// # Examples 22 | /// 23 | /// ``` 24 | /// use std::time::{SystemTime, Duration}; 25 | /// use actix_web::HttpResponse; 26 | /// use actix_web::http::header::IfModifiedSince; 27 | /// 28 | /// let mut builder = HttpResponse::Ok(); 29 | /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 30 | /// builder.insert_header( 31 | /// IfModifiedSince(modified.into()) 32 | /// ); 33 | /// ``` 34 | (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate] 35 | 36 | test_parse_and_format { 37 | // Test case from RFC 38 | crate::http::header::common_header_test!(test1, [b"Sat, 29 Oct 1994 19:43:31 GMT"]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /actix-web/src/http/header/if_unmodified_since.rs: -------------------------------------------------------------------------------- 1 | use super::{HttpDate, IF_UNMODIFIED_SINCE}; 2 | 3 | crate::http::header::common_header! { 4 | /// `If-Unmodified-Since` header, defined 5 | /// in [RFC 7232 §3.4](https://datatracker.ietf.org/doc/html/rfc7232#section-3.4) 6 | /// 7 | /// The `If-Unmodified-Since` header field makes the request method 8 | /// conditional on the selected representation's last modification date 9 | /// being earlier than or equal to the date provided in the field-value. 10 | /// This field accomplishes the same purpose as If-Match for cases where 11 | /// the user agent does not have an entity-tag for the representation. 12 | /// 13 | /// # ABNF 14 | /// ```plain 15 | /// If-Unmodified-Since = HTTP-date 16 | /// ``` 17 | /// 18 | /// # Example Values 19 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 20 | /// 21 | /// # Examples 22 | /// 23 | /// ``` 24 | /// use std::time::{SystemTime, Duration}; 25 | /// use actix_web::HttpResponse; 26 | /// use actix_web::http::header::IfUnmodifiedSince; 27 | /// 28 | /// let mut builder = HttpResponse::Ok(); 29 | /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 30 | /// builder.insert_header( 31 | /// IfUnmodifiedSince(modified.into()) 32 | /// ); 33 | /// ``` 34 | (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate] 35 | 36 | test_parse_and_format { 37 | // Test case from RFC 38 | crate::http::header::common_header_test!(test1, [b"Sat, 29 Oct 1994 19:43:31 GMT"]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /actix-web/src/http/header/last_modified.rs: -------------------------------------------------------------------------------- 1 | use super::{HttpDate, LAST_MODIFIED}; 2 | 3 | crate::http::header::common_header! { 4 | /// `Last-Modified` header, defined 5 | /// in [RFC 7232 §2.2](https://datatracker.ietf.org/doc/html/rfc7232#section-2.2) 6 | /// 7 | /// The `Last-Modified` header field in a response provides a timestamp 8 | /// indicating the date and time at which the origin server believes the 9 | /// selected representation was last modified, as determined at the 10 | /// conclusion of handling the request. 11 | /// 12 | /// # ABNF 13 | /// ```plain 14 | /// Expires = HTTP-date 15 | /// ``` 16 | /// 17 | /// # Example Values 18 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 19 | /// 20 | /// # Examples 21 | /// 22 | /// ``` 23 | /// use std::time::{SystemTime, Duration}; 24 | /// use actix_web::HttpResponse; 25 | /// use actix_web::http::header::LastModified; 26 | /// 27 | /// let mut builder = HttpResponse::Ok(); 28 | /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 29 | /// builder.insert_header( 30 | /// LastModified(modified.into()) 31 | /// ); 32 | /// ``` 33 | (LastModified, LAST_MODIFIED) => [HttpDate] 34 | 35 | test_parse_and_format { 36 | // Test case from RFC 37 | crate::http::header::common_header_test!(test1, [b"Sat, 29 Oct 1994 19:43:31 GMT"]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /actix-web/src/http/header/preference.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{self, Write as _}, 3 | str, 4 | }; 5 | 6 | /// A wrapper for types used in header values where wildcard (`*`) items are allowed but the 7 | /// underlying type does not support them. 8 | /// 9 | /// For example, we use the `language-tags` crate for the [`AcceptLanguage`](super::AcceptLanguage) 10 | /// typed header but it does not parse `*` successfully. On the other hand, the `mime` crate, used 11 | /// for [`Accept`](super::Accept), has first-party support for wildcard items so this wrapper is not 12 | /// used in those header types. 13 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Hash)] 14 | pub enum Preference { 15 | /// A wildcard value. 16 | Any, 17 | 18 | /// A valid `T`. 19 | Specific(T), 20 | } 21 | 22 | impl Preference { 23 | /// Returns true if preference is the any/wildcard (`*`) value. 24 | pub fn is_any(&self) -> bool { 25 | matches!(self, Self::Any) 26 | } 27 | 28 | /// Returns true if preference is the specific item (`T`) variant. 29 | pub fn is_specific(&self) -> bool { 30 | matches!(self, Self::Specific(_)) 31 | } 32 | 33 | /// Returns reference to value in `Specific` variant, if it is set. 34 | pub fn item(&self) -> Option<&T> { 35 | match self { 36 | Preference::Specific(ref item) => Some(item), 37 | Preference::Any => None, 38 | } 39 | } 40 | 41 | /// Consumes the container, returning the value in the `Specific` variant, if it is set. 42 | pub fn into_item(self) -> Option { 43 | match self { 44 | Preference::Specific(item) => Some(item), 45 | Preference::Any => None, 46 | } 47 | } 48 | } 49 | 50 | impl fmt::Display for Preference { 51 | #[inline] 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | match self { 54 | Preference::Any => f.write_char('*'), 55 | Preference::Specific(item) => fmt::Display::fmt(item, f), 56 | } 57 | } 58 | } 59 | 60 | impl str::FromStr for Preference { 61 | type Err = T::Err; 62 | 63 | #[inline] 64 | fn from_str(s: &str) -> Result { 65 | match s.trim() { 66 | "*" => Ok(Self::Any), 67 | other => other.parse().map(Preference::Specific), 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /actix-web/src/http/mod.rs: -------------------------------------------------------------------------------- 1 | //! Various HTTP related types. 2 | 3 | pub mod header; 4 | 5 | pub use actix_http::{uri, ConnectionType, Error, KeepAlive, Method, StatusCode, Uri, Version}; 6 | -------------------------------------------------------------------------------- /actix-web/src/middleware/authors-guide.md: -------------------------------------------------------------------------------- 1 | # Middleware Author's Guide 2 | 3 | ## What Is A Middleware? 4 | 5 | ## Middleware Traits 6 | 7 | ## Understanding Body Types 8 | 9 | ## Best Practices 10 | 11 | ## Error Propagation 12 | 13 | ## When To (Not) Use Middleware 14 | 15 | ## Author's References 16 | 17 | - `EitherBody` + when is middleware appropriate: https://discord.com/channels/771444961383153695/952016890723729428 18 | -------------------------------------------------------------------------------- /actix-web/src/middleware/identity.rs: -------------------------------------------------------------------------------- 1 | //! A no-op middleware. See [Noop] for docs. 2 | 3 | use actix_utils::future::{ready, Ready}; 4 | 5 | use crate::dev::{forward_ready, Service, Transform}; 6 | 7 | /// A no-op middleware that passes through request and response untouched. 8 | #[derive(Debug, Clone, Default)] 9 | #[non_exhaustive] 10 | pub struct Identity; 11 | 12 | impl, Req> Transform for Identity { 13 | type Response = S::Response; 14 | type Error = S::Error; 15 | type Transform = IdentityMiddleware; 16 | type InitError = (); 17 | type Future = Ready>; 18 | 19 | #[inline] 20 | fn new_transform(&self, service: S) -> Self::Future { 21 | ready(Ok(IdentityMiddleware { service })) 22 | } 23 | } 24 | 25 | #[doc(hidden)] 26 | pub struct IdentityMiddleware { 27 | service: S, 28 | } 29 | 30 | impl, Req> Service for IdentityMiddleware { 31 | type Response = S::Response; 32 | type Error = S::Error; 33 | type Future = S::Future; 34 | 35 | forward_ready!(service); 36 | 37 | #[inline] 38 | fn call(&self, req: Req) -> Self::Future { 39 | self.service.call(req) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /actix-web/src/response/mod.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod customize_responder; 3 | mod http_codes; 4 | mod responder; 5 | #[allow(clippy::module_inception)] 6 | mod response; 7 | 8 | pub use self::{ 9 | builder::HttpResponseBuilder, customize_responder::CustomizeResponder, responder::Responder, 10 | response::HttpResponse, 11 | }; 12 | -------------------------------------------------------------------------------- /actix-web/src/test/test_services.rs: -------------------------------------------------------------------------------- 1 | use actix_utils::future::ok; 2 | 3 | use crate::{ 4 | body::BoxBody, 5 | dev::{fn_service, Service, ServiceRequest, ServiceResponse}, 6 | http::StatusCode, 7 | Error, HttpResponseBuilder, 8 | }; 9 | 10 | /// Creates service that always responds with `200 OK` and no body. 11 | pub fn ok_service( 12 | ) -> impl Service, Error = Error> { 13 | status_service(StatusCode::OK) 14 | } 15 | 16 | /// Creates service that always responds with given status code and no body. 17 | pub fn status_service( 18 | status_code: StatusCode, 19 | ) -> impl Service, Error = Error> { 20 | fn_service(move |req: ServiceRequest| { 21 | ok(req.into_response(HttpResponseBuilder::new(status_code).finish())) 22 | }) 23 | } 24 | 25 | #[doc(hidden)] 26 | #[deprecated(since = "4.0.0", note = "Renamed to `status_service`.")] 27 | pub fn simple_service( 28 | status_code: StatusCode, 29 | ) -> impl Service, Error = Error> { 30 | status_service(status_code) 31 | } 32 | 33 | #[doc(hidden)] 34 | #[deprecated(since = "4.0.0", note = "Renamed to `status_service`.")] 35 | pub fn default_service( 36 | status_code: StatusCode, 37 | ) -> impl Service, Error = Error> { 38 | status_service(status_code) 39 | } 40 | -------------------------------------------------------------------------------- /actix-web/src/types/html.rs: -------------------------------------------------------------------------------- 1 | //! Semantic HTML responder. See [`Html`]. 2 | 3 | use crate::{ 4 | http::{ 5 | header::{self, ContentType, TryIntoHeaderValue}, 6 | StatusCode, 7 | }, 8 | HttpRequest, HttpResponse, Responder, 9 | }; 10 | 11 | /// Semantic HTML responder. 12 | /// 13 | /// When used as a responder, creates a 200 OK response, sets the correct HTML content type, and 14 | /// uses the string passed to [`Html::new()`] as the body. 15 | /// 16 | /// ``` 17 | /// # use actix_web::web::Html; 18 | /// Html::new("

Hello, World!

") 19 | /// # ; 20 | /// ``` 21 | #[derive(Debug, Clone, PartialEq, Hash)] 22 | pub struct Html(String); 23 | 24 | impl Html { 25 | /// Constructs a new `Html` responder. 26 | pub fn new(html: impl Into) -> Self { 27 | Self(html.into()) 28 | } 29 | } 30 | 31 | impl Responder for Html { 32 | type Body = String; 33 | 34 | fn respond_to(self, _req: &HttpRequest) -> HttpResponse { 35 | let mut res = HttpResponse::with_body(StatusCode::OK, self.0); 36 | res.headers_mut().insert( 37 | header::CONTENT_TYPE, 38 | ContentType::html().try_into_value().unwrap(), 39 | ); 40 | res 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use super::*; 47 | use crate::test::TestRequest; 48 | 49 | #[test] 50 | fn responder() { 51 | let req = TestRequest::default().to_http_request(); 52 | 53 | let res = Html::new("

Hello, World!

"); 54 | let res = res.respond_to(&req); 55 | 56 | assert!(res.status().is_success()); 57 | assert!(res 58 | .headers() 59 | .get(header::CONTENT_TYPE) 60 | .unwrap() 61 | .to_str() 62 | .unwrap() 63 | .starts_with("text/html")); 64 | assert!(res.body().starts_with("

")); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /actix-web/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | //! Common extractors and responders. 2 | 3 | mod either; 4 | mod form; 5 | mod header; 6 | mod html; 7 | mod json; 8 | mod path; 9 | mod payload; 10 | mod query; 11 | mod readlines; 12 | 13 | pub use self::{ 14 | either::Either, 15 | form::{Form, FormConfig, UrlEncoded}, 16 | header::Header, 17 | html::Html, 18 | json::{Json, JsonBody, JsonConfig}, 19 | path::{Path, PathConfig}, 20 | payload::{Payload, PayloadConfig}, 21 | query::{Query, QueryConfig}, 22 | readlines::Readlines, 23 | }; 24 | -------------------------------------------------------------------------------- /actix-web/tests/fixtures/lorem.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin interdum tincidunt lacus, sed tempor lorem consectetur et. Pellentesque et egestas sem, at cursus massa. Nunc feugiat elit sit amet ipsum commodo luctus. Proin auctor dignissim pharetra. Integer iaculis quam a tellus auctor, vitae auctor nisl viverra. Nullam consequat maximus porttitor. Pellentesque tortor enim, molestie at varius non, tempor non nibh. Suspendisse tempus erat lorem, vel faucibus magna blandit vel. Sed pellentesque ligula augue, vitae fermentum eros blandit et. Cras dignissim in massa ut varius. Vestibulum commodo nunc sit amet pellentesque dignissim. 2 | 3 | Donec imperdiet blandit lobortis. Suspendisse fringilla nunc quis venenatis tempor. Nunc tempor sed erat sed convallis. Pellentesque aliquet elit lectus, quis vulputate arcu pharetra sed. Etiam laoreet aliquet arcu cursus vehicula. Maecenas odio odio, elementum faucibus sollicitudin vitae, pellentesque ac purus. Donec venenatis faucibus lorem, et finibus lacus tincidunt vitae. Quisque laoreet metus sapien, vitae euismod mauris lobortis malesuada. Integer sit amet elementum turpis. Maecenas ex mauris, dapibus eu placerat vitae, rutrum convallis enim. Nulla vitae orci ultricies, sagittis turpis et, lacinia dui. Praesent egestas urna turpis, sit amet feugiat mauris tristique eu. Quisque id tempor libero. Donec ullamcorper dapibus lorem, vel consequat risus congue a. 4 | 5 | Nullam dignissim ut lectus vitae tempor. Pellentesque ut odio fringilla, volutpat mi et, vulputate tellus. Fusce eget diam non odio tincidunt viverra eu vel augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nullam sed eleifend purus, vitae aliquam orci. Cras fringilla justo eget tempus bibendum. Phasellus venenatis, odio nec pulvinar commodo, quam neque lacinia turpis, ut rutrum tortor massa eu nulla. Vivamus tincidunt ut lectus a gravida. Donec varius mi quis enim interdum ultrices. Sed aliquam consectetur nisi vitae viverra. Praesent nec ligula egestas, porta lectus sed, consectetur augue. 6 | -------------------------------------------------------------------------------- /actix-web/tests/fixtures/lorem.txt.br: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/actix-web/10f56f64123b06e4bc8b476761ecc3765736f480/actix-web/tests/fixtures/lorem.txt.br -------------------------------------------------------------------------------- /actix-web/tests/fixtures/lorem.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/actix-web/10f56f64123b06e4bc8b476761ecc3765736f480/actix-web/tests/fixtures/lorem.txt.gz -------------------------------------------------------------------------------- /actix-web/tests/fixtures/lorem.txt.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/actix-web/10f56f64123b06e4bc8b476761ecc3765736f480/actix-web/tests/fixtures/lorem.txt.xz -------------------------------------------------------------------------------- /actix-web/tests/fixtures/lorem.txt.zst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/actix-web/10f56f64123b06e4bc8b476761ecc3765736f480/actix-web/tests/fixtures/lorem.txt.zst -------------------------------------------------------------------------------- /actix-web/tests/test-macro-import-conflict.rs: -------------------------------------------------------------------------------- 1 | //! Checks that test macro does not cause problems in the presence of imports named "test" that 2 | //! could be either a module with test items or the "test with runtime" macro itself. 3 | //! 4 | //! Before actix/actix-net#399 was implemented, this macro was running twice. The first run output 5 | //! `#[test]` and it got run again and since it was in scope. 6 | //! 7 | //! Prevented by using the fully-qualified test marker (`#[::core::prelude::v1::test]`). 8 | 9 | use actix_web::test; 10 | 11 | #[actix_web::test] 12 | async fn test_macro_naming_conflict() { 13 | let _req = test::TestRequest::default(); 14 | assert_eq!(async { 1 }.await, 1); 15 | } 16 | -------------------------------------------------------------------------------- /actix-web/tests/test_weird_poll.rs: -------------------------------------------------------------------------------- 1 | //! Regression test for https://github.com/actix/actix-web/issues/1321 2 | 3 | // use actix_http::body::{BodyStream, MessageBody}; 4 | // use bytes::Bytes; 5 | // use futures_channel::oneshot; 6 | // use futures_util::{ 7 | // stream::once, 8 | // task::{noop_waker, Context}, 9 | // }; 10 | 11 | // #[test] 12 | // fn weird_poll() { 13 | // let (sender, receiver) = oneshot::channel(); 14 | // let mut body_stream = Ok(BodyStream::new(once(async { 15 | // let x = Box::new(0); 16 | // let y = &x; 17 | // receiver.await.unwrap(); 18 | // let _z = **y; 19 | // Ok::<_, ()>(Bytes::new()) 20 | // }))); 21 | 22 | // let waker = noop_waker(); 23 | // let mut cx = Context::from_waker(&waker); 24 | 25 | // let _ = body_stream.as_mut().unwrap().poll_next(&mut cx); 26 | // sender.send(()).unwrap(); 27 | // let _ = std::mem::replace(&mut body_stream, Err([0; 32])) 28 | // .unwrap() 29 | // .poll_next(&mut cx); 30 | // } 31 | -------------------------------------------------------------------------------- /actix-web/tests/weird_poll.rs: -------------------------------------------------------------------------------- 1 | //! Regression test for https://github.com/actix/actix-web/issues/1321 2 | 3 | // use actix_http::body::{BodyStream, MessageBody}; 4 | // use bytes::Bytes; 5 | // use futures_channel::oneshot; 6 | // use futures_util::{ 7 | // stream::once, 8 | // task::{noop_waker, Context}, 9 | // }; 10 | 11 | // #[test] 12 | // fn weird_poll() { 13 | // let (sender, receiver) = oneshot::channel(); 14 | // let mut body_stream = Ok(BodyStream::new(once(async { 15 | // let x = Box::new(0); 16 | // let y = &x; 17 | // receiver.await.unwrap(); 18 | // let _z = **y; 19 | // Ok::<_, ()>(Bytes::new()) 20 | // }))); 21 | 22 | // let waker = noop_waker(); 23 | // let mut cx = Context::from_waker(&waker); 24 | 25 | // let _ = body_stream.as_mut().unwrap().poll_next(&mut cx); 26 | // sender.send(()).unwrap(); 27 | // let _ = std::mem::replace(&mut body_stream, Err([0; 32])) 28 | // .unwrap() 29 | // .poll_next(&mut cx); 30 | // } 31 | -------------------------------------------------------------------------------- /awc/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /awc/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /awc/README.md: -------------------------------------------------------------------------------- 1 | # `awc` (Actix Web Client) 2 | 3 | > Async HTTP and WebSocket client library. 4 | 5 | 6 | 7 | [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) 8 | [![Documentation](https://docs.rs/awc/badge.svg?version=3.7.0)](https://docs.rs/awc/3.7.0) 9 | ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) 10 | [![Dependency Status](https://deps.rs/crate/awc/3.7.0/status.svg)](https://deps.rs/crate/awc/3.7.0) 11 | [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) 12 | 13 | 14 | 15 | ## Examples 16 | 17 | [Example project using TLS-enabled client →](https://github.com/actix/examples/tree/master/https-tls/awc-https) 18 | 19 | Basic usage: 20 | 21 | ```rust 22 | use actix_rt::System; 23 | use awc::Client; 24 | 25 | fn main() { 26 | System::new().block_on(async { 27 | let client = Client::default(); 28 | 29 | let res = client 30 | .get("http://www.rust-lang.org") // <- Create request builder 31 | .insert_header(("User-Agent", "Actix-web")) 32 | .send() // <- Send http request 33 | .await; 34 | 35 | println!("Response: {:?}", res); // <- server http response 36 | }); 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /awc/examples/client.rs: -------------------------------------------------------------------------------- 1 | //! Demonstrates construction and usage of a TLS-capable HTTP client. 2 | 3 | extern crate tls_rustls_0_23 as rustls; 4 | 5 | use std::{error::Error as StdError, sync::Arc}; 6 | 7 | use actix_tls::connect::rustls_0_23::webpki_roots_cert_store; 8 | use rustls::ClientConfig; 9 | 10 | #[actix_rt::main] 11 | async fn main() -> Result<(), Box> { 12 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 13 | 14 | let mut config = ClientConfig::builder() 15 | .with_root_certificates(webpki_roots_cert_store()) 16 | .with_no_client_auth(); 17 | 18 | let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; 19 | config.alpn_protocols = protos; 20 | 21 | // construct request builder with TLS support 22 | let client = awc::Client::builder() 23 | .connector(awc::Connector::new().rustls_0_23(Arc::new(config))) 24 | .finish(); 25 | 26 | // configure request 27 | let request = client 28 | .get("https://www.rust-lang.org/") 29 | .append_header(("User-Agent", "awc/3.0")); 30 | 31 | println!("Request: {request:?}"); 32 | 33 | let mut response = request.send().await?; 34 | 35 | // server response head 36 | println!("Response: {response:?}"); 37 | 38 | // read response body 39 | let body = response.body().await?; 40 | println!("Downloaded: {:?} bytes", body.len()); 41 | 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /awc/src/client/config.rs: -------------------------------------------------------------------------------- 1 | use std::{net::IpAddr, time::Duration}; 2 | 3 | const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2MB 4 | const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1MB 5 | 6 | /// Connector configuration 7 | #[derive(Clone)] 8 | pub(crate) struct ConnectorConfig { 9 | pub(crate) timeout: Duration, 10 | pub(crate) handshake_timeout: Duration, 11 | pub(crate) conn_lifetime: Duration, 12 | pub(crate) conn_keep_alive: Duration, 13 | pub(crate) disconnect_timeout: Option, 14 | pub(crate) limit: usize, 15 | pub(crate) conn_window_size: u32, 16 | pub(crate) stream_window_size: u32, 17 | pub(crate) local_address: Option, 18 | } 19 | 20 | impl Default for ConnectorConfig { 21 | fn default() -> Self { 22 | Self { 23 | timeout: Duration::from_secs(5), 24 | handshake_timeout: Duration::from_secs(5), 25 | conn_lifetime: Duration::from_secs(75), 26 | conn_keep_alive: Duration::from_secs(15), 27 | disconnect_timeout: Some(Duration::from_millis(3000)), 28 | limit: 100, 29 | conn_window_size: DEFAULT_H2_CONN_WINDOW, 30 | stream_window_size: DEFAULT_H2_STREAM_WINDOW, 31 | local_address: None, 32 | } 33 | } 34 | } 35 | 36 | impl ConnectorConfig { 37 | pub(crate) fn no_disconnect_timeout(&self) -> Self { 38 | let mut res = self.clone(); 39 | res.disconnect_timeout = None; 40 | res 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /awc/src/error.rs: -------------------------------------------------------------------------------- 1 | //! HTTP client errors 2 | 3 | // TODO: figure out how best to expose http::Error vs actix_http::Error 4 | pub use actix_http::{ 5 | error::{HttpError, PayloadError}, 6 | header::HeaderValue, 7 | ws::{HandshakeError as WsHandshakeError, ProtocolError as WsProtocolError}, 8 | StatusCode, 9 | }; 10 | use derive_more::{Display, From}; 11 | use serde_json::error::Error as JsonError; 12 | 13 | pub use crate::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; 14 | 15 | // TODO: address display, error, and from impls 16 | 17 | /// Websocket client error 18 | #[derive(Debug, Display, From)] 19 | pub enum WsClientError { 20 | /// Invalid response status 21 | #[display("Invalid response status")] 22 | InvalidResponseStatus(StatusCode), 23 | 24 | /// Invalid upgrade header 25 | #[display("Invalid upgrade header")] 26 | InvalidUpgradeHeader, 27 | 28 | /// Invalid connection header 29 | #[display("Invalid connection header")] 30 | InvalidConnectionHeader(HeaderValue), 31 | 32 | /// Missing Connection header 33 | #[display("Missing Connection header")] 34 | MissingConnectionHeader, 35 | 36 | /// Missing Sec-Websocket-Accept header 37 | #[display("Missing Sec-Websocket-Accept header")] 38 | MissingWebSocketAcceptHeader, 39 | 40 | /// Invalid challenge response 41 | #[display("Invalid challenge response")] 42 | InvalidChallengeResponse([u8; 28], HeaderValue), 43 | 44 | /// Protocol error 45 | #[display("{}", _0)] 46 | Protocol(WsProtocolError), 47 | 48 | /// Send request error 49 | #[display("{}", _0)] 50 | SendRequest(SendRequestError), 51 | } 52 | 53 | impl std::error::Error for WsClientError {} 54 | 55 | impl From for WsClientError { 56 | fn from(err: InvalidUrl) -> Self { 57 | WsClientError::SendRequest(err.into()) 58 | } 59 | } 60 | 61 | impl From for WsClientError { 62 | fn from(err: HttpError) -> Self { 63 | WsClientError::SendRequest(err.into()) 64 | } 65 | } 66 | 67 | /// A set of errors that can occur during parsing json payloads 68 | #[derive(Debug, Display, From)] 69 | pub enum JsonPayloadError { 70 | /// Content type error 71 | #[display("Content type error")] 72 | ContentType, 73 | /// Deserialize error 74 | #[display("Json deserialize error: {}", _0)] 75 | Deserialize(JsonError), 76 | /// Payload error 77 | #[display("Error that occur during reading payload: {}", _0)] 78 | Payload(PayloadError), 79 | } 80 | 81 | impl std::error::Error for JsonPayloadError {} 82 | -------------------------------------------------------------------------------- /awc/src/middleware/mod.rs: -------------------------------------------------------------------------------- 1 | mod redirect; 2 | 3 | use std::marker::PhantomData; 4 | 5 | use actix_service::Service; 6 | 7 | pub use self::redirect::Redirect; 8 | 9 | /// Trait for transform a type to another one. 10 | /// Both the input and output type should impl [actix_service::Service] trait. 11 | pub trait Transform { 12 | type Transform: Service; 13 | 14 | /// Creates and returns a new Transform component. 15 | fn new_transform(self, service: S) -> Self::Transform; 16 | } 17 | 18 | #[doc(hidden)] 19 | /// Helper struct for constructing Nested types that would call `Transform::new_transform` 20 | /// in a chain. 21 | /// 22 | /// The child field would be called first and the output `Service` type is 23 | /// passed to parent as input type. 24 | pub struct NestTransform 25 | where 26 | T1: Transform, 27 | T2: Transform, 28 | { 29 | child: T1, 30 | parent: T2, 31 | _service: PhantomData<(S, Req)>, 32 | } 33 | 34 | impl NestTransform 35 | where 36 | T1: Transform, 37 | T2: Transform, 38 | { 39 | pub(crate) fn new(child: T1, parent: T2) -> Self { 40 | NestTransform { 41 | child, 42 | parent, 43 | _service: PhantomData, 44 | } 45 | } 46 | } 47 | 48 | impl Transform for NestTransform 49 | where 50 | T1: Transform, 51 | T2: Transform, 52 | { 53 | type Transform = T2::Transform; 54 | 55 | fn new_transform(self, service: S) -> Self::Transform { 56 | let service = self.child.new_transform(service); 57 | self.parent.new_transform(service) 58 | } 59 | } 60 | 61 | /// Dummy impl for kick start `NestTransform` type in `ClientBuilder` type 62 | impl Transform for () 63 | where 64 | S: Service, 65 | { 66 | type Transform = S; 67 | 68 | fn new_transform(self, service: S) -> Self::Transform { 69 | service 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /awc/src/responses/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, io, pin::Pin, task::Context}; 2 | 3 | use actix_http::error::PayloadError; 4 | use actix_rt::time::Sleep; 5 | 6 | mod json_body; 7 | mod read_body; 8 | mod response; 9 | mod response_body; 10 | 11 | #[allow(deprecated)] 12 | pub use self::response_body::{MessageBody, ResponseBody}; 13 | pub use self::{json_body::JsonBody, response::ClientResponse}; 14 | 15 | /// Default body size limit: 2 MiB 16 | const DEFAULT_BODY_LIMIT: usize = 2 * 1024 * 1024; 17 | 18 | /// Helper enum with reusable sleep passed from `SendClientResponse`. 19 | /// 20 | /// See [`ClientResponse::_timeout`] for reason. 21 | pub(crate) enum ResponseTimeout { 22 | Disabled(Option>>), 23 | Enabled(Pin>), 24 | } 25 | 26 | impl Default for ResponseTimeout { 27 | fn default() -> Self { 28 | Self::Disabled(None) 29 | } 30 | } 31 | 32 | impl ResponseTimeout { 33 | fn poll_timeout(&mut self, cx: &mut Context<'_>) -> Result<(), PayloadError> { 34 | match *self { 35 | Self::Enabled(ref mut timeout) => { 36 | if timeout.as_mut().poll(cx).is_ready() { 37 | Err(PayloadError::Io(io::Error::new( 38 | io::ErrorKind::TimedOut, 39 | "Response Payload IO timed out", 40 | ))) 41 | } else { 42 | Ok(()) 43 | } 44 | } 45 | Self::Disabled(_) => Ok(()), 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /awc/src/responses/read_body.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use actix_http::{error::PayloadError, Payload}; 8 | use bytes::{Bytes, BytesMut}; 9 | use futures_core::{ready, Stream}; 10 | use pin_project_lite::pin_project; 11 | 12 | pin_project! { 13 | pub(crate) struct ReadBody { 14 | #[pin] 15 | pub(crate) stream: Payload, 16 | pub(crate) buf: BytesMut, 17 | pub(crate) limit: usize, 18 | } 19 | } 20 | 21 | impl ReadBody { 22 | pub(crate) fn new(stream: Payload, limit: usize) -> Self { 23 | Self { 24 | stream, 25 | buf: BytesMut::new(), 26 | limit, 27 | } 28 | } 29 | } 30 | 31 | impl Future for ReadBody 32 | where 33 | S: Stream>, 34 | { 35 | type Output = Result; 36 | 37 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 38 | let mut this = self.project(); 39 | 40 | while let Some(chunk) = ready!(this.stream.as_mut().poll_next(cx)?) { 41 | if (this.buf.len() + chunk.len()) > *this.limit { 42 | return Poll::Ready(Err(PayloadError::Overflow)); 43 | } 44 | 45 | this.buf.extend_from_slice(&chunk); 46 | } 47 | 48 | Poll::Ready(Ok(this.buf.split().freeze())) 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use static_assertions::assert_impl_all; 55 | 56 | use super::*; 57 | use crate::any_body::AnyBody; 58 | 59 | assert_impl_all!(ReadBody<()>: Unpin); 60 | assert_impl_all!(ReadBody: Unpin); 61 | } 62 | -------------------------------------------------------------------------------- /awc/tests/test_connector.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "openssl")] 2 | 3 | extern crate tls_openssl as openssl; 4 | 5 | use actix_http::HttpService; 6 | use actix_http_test::test_server; 7 | use actix_service::{map_config, ServiceFactoryExt}; 8 | use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; 9 | use openssl::{ 10 | pkey::PKey, 11 | ssl::{SslAcceptor, SslConnector, SslMethod, SslVerifyMode}, 12 | x509::X509, 13 | }; 14 | 15 | fn tls_config() -> SslAcceptor { 16 | let rcgen::CertifiedKey { cert, key_pair } = 17 | rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap(); 18 | let cert_file = cert.pem(); 19 | let key_file = key_pair.serialize_pem(); 20 | 21 | let cert = X509::from_pem(cert_file.as_bytes()).unwrap(); 22 | let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap(); 23 | 24 | let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); 25 | builder.set_certificate(&cert).unwrap(); 26 | builder.set_private_key(&key).unwrap(); 27 | 28 | builder.set_alpn_select_callback(|_, protos| { 29 | const H2: &[u8] = b"\x02h2"; 30 | if protos.windows(3).any(|window| window == H2) { 31 | Ok(b"h2") 32 | } else { 33 | Err(openssl::ssl::AlpnError::NOACK) 34 | } 35 | }); 36 | builder.set_alpn_protos(b"\x02h2").unwrap(); 37 | 38 | builder.build() 39 | } 40 | 41 | #[actix_rt::test] 42 | async fn test_connection_window_size() { 43 | let srv = test_server(|| { 44 | HttpService::build() 45 | .h2(map_config( 46 | App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), 47 | |_| AppConfig::default(), 48 | )) 49 | .openssl(tls_config()) 50 | .map_err(|_| ()) 51 | }) 52 | .await; 53 | 54 | // disable ssl verification 55 | let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); 56 | builder.set_verify(SslVerifyMode::NONE); 57 | let _ = builder 58 | .set_alpn_protos(b"\x02h2\x08http/1.1") 59 | .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); 60 | 61 | let client = awc::Client::builder() 62 | .connector(awc::Connector::new().openssl(builder.build())) 63 | .initial_window_size(100) 64 | .initial_connection_window_size(100) 65 | .finish(); 66 | 67 | let request = client.get(srv.surl("/")).send(); 68 | let response = request.await.unwrap(); 69 | assert!(response.status().is_success()); 70 | assert_eq!(response.version(), Version::HTTP_2); 71 | } 72 | -------------------------------------------------------------------------------- /awc/tests/test_ws.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use actix_codec::Framed; 4 | use actix_http::{body::BodySize, h1, ws, Error, HttpService, Request, Response}; 5 | use actix_http_test::test_server; 6 | use actix_utils::future::ok; 7 | use bytes::Bytes; 8 | use futures_util::{SinkExt as _, StreamExt as _}; 9 | 10 | async fn ws_service(req: ws::Frame) -> Result { 11 | match req { 12 | ws::Frame::Ping(msg) => Ok(ws::Message::Pong(msg)), 13 | ws::Frame::Text(text) => Ok(ws::Message::Text( 14 | String::from_utf8(Vec::from(text.as_ref())).unwrap().into(), 15 | )), 16 | ws::Frame::Binary(bin) => Ok(ws::Message::Binary(bin)), 17 | ws::Frame::Close(reason) => Ok(ws::Message::Close(reason)), 18 | _ => Ok(ws::Message::Close(None)), 19 | } 20 | } 21 | 22 | #[actix_rt::test] 23 | async fn test_simple() { 24 | let mut srv = test_server(|| { 25 | HttpService::build() 26 | .upgrade(|(req, mut framed): (Request, Framed<_, _>)| { 27 | async move { 28 | let res = ws::handshake_response(req.head()).finish(); 29 | // send handshake response 30 | framed 31 | .send(h1::Message::Item((res.drop_body(), BodySize::None))) 32 | .await?; 33 | 34 | // start WebSocket service 35 | let framed = framed.replace_codec(ws::Codec::new()); 36 | ws::Dispatcher::with(framed, ws_service).await 37 | } 38 | }) 39 | .finish(|_| ok::<_, Error>(Response::not_found())) 40 | .tcp() 41 | }) 42 | .await; 43 | 44 | // client service 45 | let mut framed = srv.ws().await.unwrap(); 46 | framed.send(ws::Message::Text("text".into())).await.unwrap(); 47 | let item = framed.next().await.unwrap().unwrap(); 48 | assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); 49 | 50 | framed 51 | .send(ws::Message::Binary("text".into())) 52 | .await 53 | .unwrap(); 54 | let item = framed.next().await.unwrap().unwrap(); 55 | assert_eq!(item, ws::Frame::Binary(Bytes::from_static(b"text"))); 56 | 57 | framed.send(ws::Message::Ping("text".into())).await.unwrap(); 58 | let item = framed.next().await.unwrap().unwrap(); 59 | assert_eq!(item, ws::Frame::Pong("text".to_string().into())); 60 | 61 | framed 62 | .send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))) 63 | .await 64 | .unwrap(); 65 | 66 | let item = framed.next().await.unwrap().unwrap(); 67 | assert_eq!(item, ws::Frame::Close(Some(ws::CloseCode::Normal.into()))); 68 | } 69 | -------------------------------------------------------------------------------- /docs/graphs/.gitignore: -------------------------------------------------------------------------------- 1 | # do not track rendered graphs 2 | *.png 3 | -------------------------------------------------------------------------------- /docs/graphs/README.md: -------------------------------------------------------------------------------- 1 | # Actix Ecosystem Dependency Graphs 2 | 3 | See rendered versions of these dot graphs [on the wiki](https://github.com/actix/actix-web/wiki/Dependency-Graph). 4 | 5 | ## Rendering 6 | 7 | Dot graphs were rendered using the `dot` command from [GraphViz](https://www.graphviz.org/doc/info/command.html): 8 | 9 | ```sh 10 | for f in $(ls docs/graphs/*.dot | xargs); do dot $f -Tpng -o${f:r}.png; done 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/graphs/net-only.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | rankdir=TB 3 | 4 | subgraph cluster_net { 5 | label="actix-net" 6 | "actix-codec" "actix-macros" "actix-rt" "actix-server" "actix-service" 7 | "actix-tls" "actix-tracing" "actix-utils" 8 | } 9 | 10 | subgraph cluster_other { 11 | label="other actix owned crates" 12 | { rank=same; "local-channel" "local-waker" "bytestring" } 13 | } 14 | 15 | subgraph cluster_tokio { 16 | label="tokio" 17 | "tokio" "tokio-util" 18 | } 19 | 20 | "actix-codec" -> { "tokio" } 21 | "actix-codec" -> { "tokio-util" }[color=red] 22 | "actix-utils" -> { "local-waker" } 23 | "actix-tracing" -> { "actix-service" } 24 | "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } 25 | "actix-tls" -> { "tokio-util" }[color="#009900"] 26 | "actix-server" -> { "actix-service" "actix-rt" "actix-utils" "tokio" } 27 | "actix-rt" -> { "actix-macros" "tokio" } 28 | 29 | "local-channel" -> { "local-waker" } 30 | 31 | // invisible edges to force nicer layout 32 | edge [style=invis] 33 | "actix-macros" -> "tokio" 34 | "actix-service" -> "bytestring" 35 | "actix-macros" -> "bytestring" 36 | } 37 | -------------------------------------------------------------------------------- /docs/graphs/web-focus.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | subgraph cluster_web { 3 | label="actix/web" 4 | 5 | "awc" 6 | "web" 7 | "files" 8 | "http" 9 | "multipart" 10 | "web-actors" 11 | "web-codegen" 12 | "http-test" 13 | "router" 14 | 15 | { rank=same; "multipart" "web-actors" "http-test" }; 16 | { rank=same; "files" "awc" "web" }; 17 | { rank=same; "web-codegen" "http" }; 18 | } 19 | 20 | "web" -> { "codec" "service" "utils" "router" "rt" "server" "macros" "web-codegen" "http" "awc" } 21 | "web" -> { "tls" }[color=blue] // optional 22 | "web-codegen" -> { "router" } 23 | "awc" -> { "codec" "service" "http" "rt" } 24 | "web-actors" -> { "actix" "web" "http" "codec" } 25 | "multipart" -> { "web" "service" "utils" } 26 | "http" -> { "service" "codec" "utils" "rt" } 27 | "http" -> { "tls" }[color=blue] // optional 28 | "files" -> { "web" } 29 | "http-test" -> { "service" "codec" "utils" "rt" "server" "awc" } 30 | "http-test" -> { "tls" }[color=blue] // optional 31 | 32 | // net 33 | 34 | "utils" -> { "service" "rt" "codec" } 35 | "tracing" -> { "service" } 36 | "tls" -> { "service" "codec" "utils" } 37 | "server" -> { "service" "rt" "utils" } 38 | "rt" -> { "macros" } 39 | 40 | { rank=same; "utils" "codec" }; 41 | { rank=same; "rt" "macros" "service" }; 42 | 43 | // actix 44 | 45 | "actix" -> { "rt" } 46 | } 47 | -------------------------------------------------------------------------------- /docs/graphs/web-only.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | subgraph cluster_web { 3 | label="actix/actix-web" 4 | "awc" 5 | "web" 6 | "files" 7 | "http" 8 | "multipart" 9 | "web-actors" 10 | "web-codegen" 11 | "http-test" 12 | "test" 13 | "router" 14 | } 15 | 16 | "web" -> { "web-codegen" "http" "router" } 17 | "awc" -> { "http" } 18 | "web-codegen" -> { "router" }[color = red] 19 | "web-actors" -> { "actix" "web" "http" } 20 | "multipart" -> { "web" } 21 | "files" -> { "web" } 22 | "http-test" -> { "awc" } 23 | "test" -> { "web" "awc" "http-test" } 24 | } 25 | -------------------------------------------------------------------------------- /scripts/ci-test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # run tests matching what CI does for non-linux feature sets 4 | 5 | set -x 6 | 7 | EXIT=0 8 | 9 | save_exit_code() { 10 | eval $@ 11 | local CMD_EXIT=$? 12 | [ "$CMD_EXIT" = "0" ] || EXIT=$CMD_EXIT 13 | } 14 | 15 | save_exit_code cargo test --lib --tests -p=actix-router --all-features -- --nocapture 16 | save_exit_code cargo test --lib --tests -p=actix-http --all-features -- --nocapture 17 | save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --nocapture 18 | save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features -- --nocapture 19 | save_exit_code cargo test --lib --tests -p=awc --all-features -- --nocapture 20 | save_exit_code cargo test --lib --tests -p=actix-http-test --all-features -- --nocapture 21 | save_exit_code cargo test --lib --tests -p=actix-test --all-features -- --nocapture 22 | save_exit_code cargo test --lib --tests -p=actix-files -- --nocapture 23 | save_exit_code cargo test --lib --tests -p=actix-multipart --all-features -- --nocapture 24 | save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features -- --nocapture 25 | 26 | save_exit_code cargo test --workspace --doc 27 | 28 | if [ "$EXIT" = "0" ]; then 29 | PASSED="All tests passed!" 30 | 31 | if [ "$(command -v figlet)" ]; then 32 | figlet "$PASSED" 33 | else 34 | echo "$PASSED" 35 | fi 36 | fi 37 | 38 | exit $EXIT 39 | -------------------------------------------------------------------------------- /scripts/free-disk-space.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | # The Azure provided machines typically have the following disk allocation: 19 | # Total space: 85GB 20 | # Allocated: 67 GB 21 | # Free: 17 GB 22 | # This script frees up 28 GB of disk space by deleting unneeded packages and 23 | # large directories. 24 | # The Flink end to end tests download and generate more than 17 GB of files, 25 | # causing unpredictable behavior and build failures. 26 | 27 | echo "==============================================================================" 28 | echo "Freeing up disk space on CI system" 29 | echo "==============================================================================" 30 | 31 | echo "Listing 100 largest packages" 32 | dpkg-query -Wf '${Installed-Size}\t${Package}\n' | sort -n | tail -n 100 33 | df -h 34 | 35 | echo "Removing large packages" 36 | sudo apt-get remove -y '^dotnet-.*' 37 | sudo apt-get remove -y 'php.*' 38 | sudo apt-get remove -y '^mongodb-.*' 39 | sudo apt-get remove -y '^mysql-.*' 40 | sudo apt-get remove -y azure-cli google-cloud-sdk hhvm google-chrome-stable firefox powershell mono-devel libgl1-mesa-dri 41 | sudo apt-get autoremove -y 42 | sudo apt-get clean 43 | df -h 44 | 45 | echo "Removing large directories" 46 | sudo rm -rf /usr/share/dotnet/ 47 | sudo rm -rf /usr/local/graalvm/ 48 | sudo rm -rf /usr/local/.ghcup/ 49 | sudo rm -rf /usr/local/share/powershell 50 | sudo rm -rf /usr/local/share/chromium 51 | sudo rm -rf /usr/local/lib/android 52 | sudo rm -rf /usr/local/lib/node_modules 53 | df -h 54 | -------------------------------------------------------------------------------- /scripts/unreleased: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -euo pipefail 4 | 5 | bold="\033[1m" 6 | reset="\033[0m" 7 | 8 | unreleased_for() { 9 | DIR=$1 10 | 11 | CARGO_MANIFEST=$DIR/Cargo.toml 12 | 13 | # determine changelog file name 14 | if [ -f "$DIR/CHANGES.md" ]; then 15 | CHANGELOG_FILE=$DIR/CHANGES.md 16 | elif [ -f "$DIR/CHANGELOG.md" ]; then 17 | CHANGELOG_FILE=$DIR/CHANGELOG.md 18 | else 19 | echo "No changelog file found" 20 | exit 1 21 | fi 22 | 23 | # get current version 24 | PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)" 25 | CURRENT_VERSION="$(sed -nE 's/^version ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST")" 26 | 27 | CHANGE_CHUNK_FILE="$(mktemp)" 28 | 29 | # get changelog chunk and save to temp file 30 | cat "$CHANGELOG_FILE" | 31 | # skip up to unreleased heading 32 | sed '1,/Unreleased/ d' | 33 | # take up to previous version heading 34 | sed "/$CURRENT_VERSION/ q" | 35 | # drop last line 36 | sed '$d' \ 37 | >"$CHANGE_CHUNK_FILE" 38 | 39 | # if word count of changelog chunk is 0 then exit 40 | if [ "$(wc -w "$CHANGE_CHUNK_FILE" | awk '{ print $1 }')" = "0" ]; then 41 | return 0; 42 | fi 43 | 44 | echo "${bold}# ${PACKAGE_NAME}${reset} since ${bold}v$CURRENT_VERSION${reset}" 45 | cat "$CHANGE_CHUNK_FILE" 46 | } 47 | 48 | files=$(fd --threads=1 --min-depth=2 --absolute-path 'CHANGE\w+.md') 49 | 50 | for f in $files; do 51 | unreleased_for $(dirname $f) || true 52 | done 53 | --------------------------------------------------------------------------------