1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-04-21 17:46:49 +02:00

Compare commits

..

130 Commits

Author SHA1 Message Date
dependabot[bot]
c04cc19e73
build(deps): bump anyhow from 1.0.97 to 1.0.98 (#526)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.97 to 1.0.98.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.97...1.0.98)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.98
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 15:19:05 +00:00
Rob Ede
6a13b3b182
chore: update deps 2025-04-09 21:52:16 +01:00
dependabot[bot]
d994912ac2
build(deps): bump openssl from 0.10.71 to 0.10.72 (#521)
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.71 to 0.10.72.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.71...openssl-v0.10.72)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.72
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-05 21:20:00 +00:00
dependabot[bot]
5f6f20cf37
build(deps): bump taiki-e/install-action from 2.49.9 to 2.49.42 (#520)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.49.9 to 2.49.42.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.49.9...v2.49.42)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-03 16:34:01 +00:00
dependabot[bot]
5145924410
build(deps): bump once_cell from 1.21.1 to 1.21.3 (#519)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.21.1 to 1.21.3.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.1...v1.21.3)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 16:44:02 +00:00
dependabot[bot]
b20dec36ac
build(deps): bump time from 0.3.39 to 0.3.41 (#515)
Bumps [time](https://github.com/time-rs/time) from 0.3.39 to 0.3.41.
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](https://github.com/time-rs/time/compare/v0.3.39...v0.3.41)

---
updated-dependencies:
- dependency-name: time
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 00:37:20 +00:00
dependabot[bot]
f6e45d487b
build(deps): bump reqwest from 0.12.14 to 0.12.15 (#516)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.12.14 to 0.12.15.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.14...v0.12.15)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 00:37:07 +00:00
dependabot[bot]
c53e198ea7
build(deps): bump redis from 0.29.1 to 0.29.2 (#517)
Bumps [redis](https://github.com/redis-rs/redis-rs) from 0.29.1 to 0.29.2.
- [Release notes](https://github.com/redis-rs/redis-rs/releases)
- [Commits](https://github.com/redis-rs/redis-rs/compare/redis-0.29.1...redis-0.29.2)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 00:36:54 +00:00
dependabot[bot]
4d9984ee76
build(deps): bump log from 0.4.26 to 0.4.27 (#518)
Bumps [log](https://github.com/rust-lang/log) from 0.4.26 to 0.4.27.
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.26...0.4.27)

---
updated-dependencies:
- dependency-name: log
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 00:36:08 +00:00
dependabot[bot]
9a08090709
build(deps): bump once_cell from 1.21.0 to 1.21.1 (#511)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.21.0 to 1.21.1.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.21.0...v1.21.1)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 02:04:49 +00:00
dependabot[bot]
7d3348bb29
build(deps): bump reqwest from 0.12.12 to 0.12.14 (#512)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.12.12 to 0.12.14.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/v0.12.14/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.12...v0.12.14)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 02:04:41 +00:00
dependabot[bot]
c0fa63af39
build(deps): bump uuid from 1.15.1 to 1.16.0 (#513)
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.15.1 to 1.16.0.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.15.1...v1.16.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-20 02:04:39 +00:00
Rob Ede
0b5e2b3647
chore: check in lockfile 2025-03-11 03:12:27 +00:00
Rob Ede
b95595b9cd
chore(actix-cors): prepare release 0.7.1 2025-03-11 02:55:33 +00:00
dependabot[bot]
4b3f87e915
build(deps): update redis requirement from 0.28 to 0.29 (#510)
* build(deps): update redis requirement from 0.28 to 0.29

Updates the requirements on [redis](https://github.com/redis-rs/redis-rs) to permit the latest version.
- [Release notes](https://github.com/redis-rs/redis-rs/releases)
- [Commits](https://github.com/redis-rs/redis-rs/compare/redis-0.28.0...redis-0.29.1)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update deadpool-redis to 0.20

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2025-03-11 02:38:37 +00:00
dependabot[bot]
144c7f92b9
build(deps): bump taiki-e/install-action from 2.47.32 to 2.49.9 (#505)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.47.32 to 2.49.9.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.47.32...v2.49.9)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-01 21:13:49 +00:00
dependabot[bot]
c71b9dd443
build(deps): bump codecov/codecov-action from 5.3.1 to 5.4.0 (#506)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.3.1...v5.4.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-01 21:13:17 +00:00
dependabot[bot]
282d56e96b
build(deps): bump actions-rust-lang/setup-rust-toolchain from 1.10.1 to 1.11.0 (#507)
* build(deps): bump actions-rust-lang/setup-rust-toolchain

Bumps [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain) from 1.10.1 to 1.11.0.
- [Release notes](https://github.com/actions-rust-lang/setup-rust-toolchain/releases)
- [Changelog](https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-rust-lang/setup-rust-toolchain/compare/v1.10.1...v1.11.0)

---
updated-dependencies:
- dependency-name: actions-rust-lang/setup-rust-toolchain
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build: lower msrv deps

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2025-03-01 21:01:02 +00:00
dependabot[bot]
d514ad3af5
build(deps): update rand requirement from 0.8 to 0.9 (#498)
* build(deps): update rand requirement from 0.8 to 0.9

Updates the requirements on [rand](https://github.com/rust-random/rand) to permit the latest version.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.8.0...0.9.0)

---
updated-dependencies:
- dependency-name: rand
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: fix rand upgrade items

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2025-02-23 18:57:16 +00:00
Rob Ede
109e6a4793
ci: fix test-msrv recipe 2025-02-23 18:53:00 +00:00
Rob Ede
bb0c7f21d9
ci: fix msrv by downgrading native-tls 2025-02-23 18:49:47 +00:00
Rob Ede
3f7a479a76
chore: update redis dependency to 0.28 2025-02-23 18:42:55 +00:00
Rob Ede
fc4b656c3b
chore: address clippy lints 2025-02-23 18:29:35 +00:00
dependabot[bot]
0f35de7da1
build(deps): update derive_more requirement from 1 to 2 (#502)
Updates the requirements on [derive_more](https://github.com/JelteF/derive_more) to permit the latest version.
- [Release notes](https://github.com/JelteF/derive_more/releases)
- [Changelog](https://github.com/JelteF/derive_more/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JelteF/derive_more/compare/v1.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: derive_more
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-10 17:17:28 +00:00
dependabot[bot]
8294fcc645
build(deps): bump taiki-e/install-action from 2.47.2 to 2.47.32 (#499)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.47.2 to 2.47.32.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.47.2...v2.47.32)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 18:27:04 +00:00
dependabot[bot]
3de6b03711
build(deps): bump codecov/codecov-action from 5.1.2 to 5.3.1 (#500)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.1.2 to 5.3.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.1.2...v5.3.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-03 18:26:46 +00:00
Wren Turkal
64931189c7
Improve logout example (#496)
The current example for logout is not show a complete example.

I have added a couple lines to handle the case where a logged out user tries to logout again.
2025-01-16 11:56:12 +00:00
Keith Cirkel
265b213123
implement contains_key, update, update_or (#459)
* implement contains_key, update, update_or

* docs(session): update docs for new methods

* docs(session): clarify errors

* test(session): fix doctest

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2025-01-14 00:51:40 +00:00
Frank Elsinga
695369f02f
feat: added PartialEq to Cors (#486)
* added PartialEq to Cors

* added a changelog entry

* re-ran rustfmt

* removed a subtle bug in the new testcase

* removed a not so subtle bug in the new testcase

* ci: rm public-api-diff job

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2025-01-13 23:22:00 +00:00
laurens-dg
87d9e51112
docs(readme): fix typo and consistent dots in table (#494) 2025-01-10 13:31:31 +00:00
dependabot[bot]
8c11d37dda
build(deps): bump codecov/codecov-action from 5.0.7 to 5.1.2 (#492)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.0.7 to 5.1.2.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.0.7...v5.1.2)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-03 16:11:53 +00:00
dependabot[bot]
d97b36652a
build(deps): bump taiki-e/install-action from 2.45.13 to 2.47.2 (#493)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.45.13 to 2.47.2.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.45.13...v2.47.2)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-03 16:11:43 +00:00
Alex Wied
98847b9279
fix: ensure TCP connection is properly shut down when Session is dropped (#476)
* Ensure TCP connection is properly shut down when session is dropped

* Update CHANGELOG.md

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-12-29 16:57:56 +00:00
vgwidt
cd1b77134e
docs: fix redis tls feature names (#487)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-12-29 16:54:31 +00:00
Rob Ede
105932706d
chore: address clippy lints 2024-12-29 16:19:36 +00:00
Necdet Arda Etiman
18f94fa8b5
Added actix-jwt-cookies and actix-ws-broadcaster to README (#482)
* Added ctix-jwt-cookies to README

* added actix-ws-broadcaster to Community Crates

---------

Co-authored-by: Necdet Arda Etiman <necdetarda123@gmail.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-12-02 08:31:00 +00:00
dependabot[bot]
66b82f0f30
build(deps): bump actions-rust-lang/setup-rust-toolchain (#478)
Bumps [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain) from 1.10.0 to 1.10.1.
- [Release notes](https://github.com/actions-rust-lang/setup-rust-toolchain/releases)
- [Changelog](https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-rust-lang/setup-rust-toolchain/compare/v1.10.0...v1.10.1)

---
updated-dependencies:
- dependency-name: actions-rust-lang/setup-rust-toolchain
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 08:28:15 +00:00
dependabot[bot]
d67abde5f3
build(deps): bump codecov/codecov-action from 4.6.0 to 5.0.7 (#483)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.6.0 to 5.0.7.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.6.0...v5.0.7)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 08:28:00 +00:00
Rob Ede
3eafe7f5ce
ci: fix public api diff 2024-12-02 10:21:30 +02:00
Rob Ede
3b5f7ae68c
docs: update readme 2024-12-02 10:21:30 +02:00
dependabot[bot]
036af488fd
build(deps): bump taiki-e/install-action from 2.44.15 to 2.45.13 (#484)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.44.15 to 2.45.13.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.44.15...v2.45.13)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 07:55:04 +00:00
Ross
77406cbb71
Added actix-web-validation to README (#474) 2024-10-13 16:32:52 +00:00
dependabot[bot]
2ede588693
build(deps): update redis requirement from 0.26 to 0.27 (#463)
* build(deps): update redis requirement from 0.26 to 0.27

Updates the requirements on [redis](https://github.com/redis-rs/redis-rs) to permit the latest version.
- [Release notes](https://github.com/redis-rs/redis-rs/releases)
- [Commits](https://github.com/redis-rs/redis-rs/compare/redis-0.26.0...redis-0.27.2)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs: update changelogs

* ci: fix doc

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-10-11 13:10:39 +00:00
jwiesler
21680e0ebe
Include cookie expiration and ttl in the identity example (#473) 2024-10-07 22:57:22 +00:00
dependabot[bot]
370f9d3033
build(deps): bump actions-rust-lang/setup-rust-toolchain (#469)
Bumps [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/actions-rust-lang/setup-rust-toolchain/releases)
- [Changelog](https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-rust-lang/setup-rust-toolchain/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: actions-rust-lang/setup-rust-toolchain
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 22:27:47 +00:00
dependabot[bot]
8f4fb348b3
build(deps): bump taiki-e/install-action from 2.42.37 to 2.44.15 (#471)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.42.37 to 2.44.15.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.42.37...v2.44.15)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 22:27:34 +00:00
dependabot[bot]
ff4b173716
build(deps): bump codecov/codecov-action from 4.5.0 to 4.6.0 (#470)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.5.0...v4.6.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 22:27:21 +00:00
William Desportes
49aacfce9f
Update README.md (#466) 2024-09-28 00:45:02 +00:00
Rob Ede
dd20ebb6cb
chore: allow missing docs on test mod 2024-09-12 15:10:28 -04:00
Rob Ede
a3211b73d3
chore(actix-identity): prepare release 0.8.0 2024-09-12 15:07:36 -04:00
Rob Ede
a89d3a58bc
refactor: fix nightly warning 2024-09-12 15:06:22 -04:00
Rob Ede
3c640ec120
chore: fix rand optionality 2024-09-12 14:56:33 -04:00
Rob Ede
26ccf8b200
chore: fix feature gate 2024-09-12 14:55:04 -04:00
Rob Ede
dd1421f1a0
chore(actix-session): prepare release 0.10.1 2024-09-12 14:50:48 -04:00
dependabot[bot]
4eb779be77
build(deps): bump taiki-e/install-action from 2.42.14 to 2.42.37 (#460)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.42.14 to 2.42.37.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.42.14...v2.42.37)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 03:02:50 +00:00
John Vandenberg
48646d1bd3
build(deps): update derive_more to v1.0 (#458)
* build(deps): update derive_more to v1.0

* chore: remove overspecified deps

* chore: use from the derive module

* chore: restore unrelated version reqs

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-08-18 14:21:56 +00:00
Rob Ede
275675e1c2
docs(settings): fix doc test 2024-08-07 01:37:53 +01:00
Rob Ede
50d2fee4e2
chore(actix-settings): prepare release 0.8.0 2024-08-07 01:32:49 +01:00
Rob Ede
0c0d13be12
docs: update session dependants changelogs 2024-08-07 01:05:57 +01:00
Rob Ede
d10b71fe06
docs(session): doc adding features using cargo add 2024-08-07 01:03:40 +01:00
Rob Ede
f2339971cd
chore(actix-session): prepare release 0.10.0 2024-08-07 00:57:54 +01:00
dependabot[bot]
517e72f248
build(deps): update reqwest requirement from 0.11 to 0.12 (#454)
* build(deps): update reqwest requirement from 0.11 to 0.12

Updates the requirements on [reqwest](https://github.com/seanmonstar/reqwest) to permit the latest version.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.0...v0.12.5)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: use reqwest::StatusCode

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-08-06 14:19:02 +00:00
João Fernandes
504e89403b
feature(session): add deadpool-redis compatibility (#381)
* Add compatibility of `deadpool-redis` for the storage `redis_rs`.

* Keep up-to-date the `actix-redis` version.

* Format the project issued by command `cargo +nightly fmt`.

* Add `deadpool-redis` into the documentation and tests.

* Update CHANGES.md.

* Update the documentation of `Deadpool Redis` section on `redis_rs`.

* Replace `no_run` with `ignore` attribute on "Deadpool Redis" example to skip the doc tests failure.

* Rollback the renaming `redis::cmd` to `cmd` for better reading and avoid shadowing, fix the wrong return type on builder function comment.

* Format the project issued by command `cargo +nightly fmt`.

* Format.

* Fix feature naming from the last merge.

* Fix feature missing from the last merge.

* Format the project issued by command `cargo +nightly fmt`.

* Re-import `cookie-session` feature. (Maybe was removed accidentally from the last merge?)

* tmp

* chore: bump deadpool-redis to 0.16

* chore: fixup rest of redis code for pool

* fix: add missing cfg guard

* docs: fix pool docs

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-08-06 13:47:49 +00:00
João Fernandes
31b1dc5aa8
feature(settings): add TLS (#380)
* Complete the missing TLS feature.

* Make the `cfg` attributes more clear.

* Format the project issued by command `cargo +nightly fmt`.

* Small changes on cargo file.

* Update CHANGES.md.

* Add documentation for `Tls::get_ssl_acceptor_builder()` and remove unused imports.

* Add the `cfg` macro with required feature on `TLS` tests.

* Update actix-settings/src/settings/tls.rs

Co-authored-by: Rob Ede <robjtede@icloud.com>

* Copy the workflow steps related to OpenSSL for windows from [actix-web workflow](a7375b6876/.github/workflows/ci.yml (L38-L45)).

* ci: install openssl 1.1.1

* Replaced `apply_settings` with `try_apply_settings` for a better error handling.

* Updated the example.

* Add `OpenSSL` error.

* Restrict `OpenSSL` error only for `tls` feature.

* Rename feature `tls` to `openssl`.

* Add doc feature `broken_intra_doc_links` to `get_ssl_acceptor_builder` function.

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-08-03 08:59:13 +00:00
dependabot[bot]
d7daf441d1
build(deps): bump taiki-e/install-action from 2.41.7 to 2.42.14 (#453)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-03 09:51:46 +01:00
dependabot[bot]
2de4b1886c
build(deps): update redis requirement from 0.25 to 0.26 (#451)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-07-30 12:05:02 +01:00
Rob Ede
caa5dbc5b3
chore: fix re-exports 2024-07-29 22:00:31 +01:00
Rob Ede
c259e715f8
chore: fix just doc 2024-07-29 21:59:53 +01:00
edgerunnergit
d8a86751f0
Make generate_session_key() public (#449)
* make generate_session_key() public and change impl to use DistString

* add changelong and use nightly fmt

* Add better support for receiving larger payloads (#430)

* Add better support for receiving larger payloads

This change enables the maximum frame size to be configured when receiving websocket frames. It also
adds a new stream time that aggregates continuation frames together into their proper collected
representation. It provides no mechanism yet for sending continuations.

* actix-ws: Add continuation & size config to changelog

* actix-ws: Add Debug, Eq to AggregatedMessage

* actix-ws: Add a configurable maximum size to aggregated continuations

* refactor: move aggregate types to own module

* test: fix chat example

* docs: update changelog

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>

* docs(ws): update readme

* chore(actix-ws): prepare release 0.3.0

* chore(ws): remove unused dev dep

* chore: expose generate_session_key

* chore: fix import

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
Co-authored-by: asonix <asonix@asonix.dog>
2024-07-29 20:53:18 +00:00
Rob Ede
cac93d2bc7
chore(ws): remove unused dev dep 2024-07-20 07:32:11 +01:00
Rob Ede
95f4e0f692
chore(actix-ws): prepare release 0.3.0 2024-07-20 07:24:20 +01:00
Rob Ede
24f3985eab
docs(ws): update readme 2024-07-20 07:23:21 +01:00
asonix
b0d2947a4a
Add better support for receiving larger payloads (#430)
* Add better support for receiving larger payloads

This change enables the maximum frame size to be configured when receiving websocket frames. It also
adds a new stream time that aggregates continuation frames together into their proper collected
representation. It provides no mechanism yet for sending continuations.

* actix-ws: Add continuation & size config to changelog

* actix-ws: Add Debug, Eq to AggregatedMessage

* actix-ws: Add a configurable maximum size to aggregated continuations

* refactor: move aggregate types to own module

* test: fix chat example

* docs: update changelog

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-07-20 06:09:30 +00:00
Rob Ede
0802eff40d
chore: fix fmt recipe 2024-07-20 07:11:31 +01:00
dependabot[bot]
2a6a36af23
build(deps): update prost requirement from 0.12 to 0.13 (#450)
* build(deps): update prost requirement from 0.12 to 0.13

Updates the requirements on [prost](https://github.com/tokio-rs/prost) to permit the latest version.
- [Release notes](https://github.com/tokio-rs/prost/releases)
- [Changelog](https://github.com/tokio-rs/prost/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/prost/compare/v0.12.0...v0.13.1)

---
updated-dependencies:
- dependency-name: prost
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs: update changelog

* chore(actix-protobuf): prepare release 0.11.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-07-20 05:16:26 +00:00
dependabot[bot]
3ebdc6192c
build(deps): bump codecov/codecov-action from 4.4.1 to 4.5.0 (#446)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.4.1 to 4.5.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.4.1...v4.5.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 17:32:54 +00:00
dependabot[bot]
87cf947a45
build(deps): bump taiki-e/cache-cargo-install-action from 2.0.0 to 2.0.1 (#447)
Bumps [taiki-e/cache-cargo-install-action](https://github.com/taiki-e/cache-cargo-install-action) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/taiki-e/cache-cargo-install-action/releases)
- [Changelog](https://github.com/taiki-e/cache-cargo-install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/cache-cargo-install-action/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: taiki-e/cache-cargo-install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 17:32:50 +00:00
dependabot[bot]
f063bec5ba
build(deps): bump taiki-e/install-action from 2.38.0 to 2.41.7 (#448)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.38.0 to 2.41.7.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.38.0...v2.41.7)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 17:32:48 +00:00
Rob Ede
45e9e00285
ci: fix coverage job 2024-06-20 03:10:20 +01:00
Rob Ede
6934db623b
ci: fix coverage job 2024-06-20 03:06:57 +01:00
Rob Ede
1a658a98e1
ci: fix coverage 2024-06-20 03:04:20 +01:00
Rob Ede
2a092a19a8
ci: coverage via just 2024-06-20 02:57:44 +01:00
Rob Ede
032aeb6fdb
chore: fix nightly warning 2024-06-20 02:28:58 +01:00
Rob Ede
52e58610e4
chore: share repo and website 2024-06-20 02:18:37 +01:00
Rob Ede
023158cfa8
chore: move lints to manifest 2024-06-20 02:14:35 +01:00
Rob Ede
14c605fae2
docs: indicative mood 2024-06-20 02:10:19 +01:00
Rob Ede
d94c023bf9
build: group recipes 2024-06-20 01:58:25 +01:00
Rob Ede
7e21fd753e
docs: clean up ws examples 2024-06-20 01:55:14 +01:00
Rob Ede
e7ee2a06ab
docs: split chat example 2024-06-20 01:37:37 +01:00
Rob Ede
8aa2c959c4
chore(actix-web-httpauth): prepare release 0.8.2 2024-06-11 04:02:32 +01:00
Rob Ede
2f1d1daee8
ci: fix doctest job 2024-06-11 03:56:49 +01:00
Rob Ede
d15572b501
ci: remove doc upload workflow 2024-06-11 03:56:08 +01:00
Rob Ede
b9e47d61c3
docs(httpauth): add HttpAuthentication::with_fn examples 2024-06-11 03:55:41 +01:00
Rob Ede
515a727ca3
docs(httpauth): rework example 2024-06-11 03:52:45 +01:00
Rob Ede
20234ec555
ci: fail on doc error 2024-06-11 03:52:15 +01:00
Rob Ede
e4bb5ed355
build: group recipes 2024-06-11 03:52:02 +01:00
dependabot[bot]
5368569d00
build(deps): bump actions-rust-lang/setup-rust-toolchain (#442)
Bumps [actions-rust-lang/setup-rust-toolchain](https://github.com/actions-rust-lang/setup-rust-toolchain) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/actions-rust-lang/setup-rust-toolchain/releases)
- [Changelog](https://github.com/actions-rust-lang/setup-rust-toolchain/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions-rust-lang/setup-rust-toolchain/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: actions-rust-lang/setup-rust-toolchain
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-09 23:00:01 +00:00
dependabot[bot]
21b9408a23
build(deps): bump taiki-e/install-action from 2.34.1 to 2.38.0 (#443)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.34.1 to 2.38.0.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.34.1...v2.38.0)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-09 22:59:50 +00:00
Rob Ede
5879740322
ci: monthly GHA updates 2024-06-09 23:40:37 +01:00
Rob Ede
4adc9f8884
docs(session): use standard docs 2024-06-09 23:38:33 +01:00
dependabot[bot]
abf75eeb06
build(deps): update redis requirement from 0.24 to 0.25 (#412)
* build(deps): update redis requirement from 0.24 to 0.25

Updates the requirements on [redis](https://github.com/redis-rs/redis-rs) to permit the latest version.
- [Release notes](https://github.com/redis-rs/redis-rs/releases)
- [Commits](https://github.com/redis-rs/redis-rs/compare/redis-0.24.0...redis-0.25.0)

---
updated-dependencies:
- dependency-name: redis
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* refactor(session): rename TLS features

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-09 22:29:13 +00:00
Rob Ede
433c926503
chore: address clippy lints 2024-06-09 23:08:20 +01:00
Rob Ede
8ebb12b75a
refactor: use tracing in session examples 2024-06-09 20:34:09 +01:00
zbigniewzolnierowicz
931c4eea4d
feat(session): add rustls (actix#342) (#402)
* feat(session): add rustls feature (actix#342)

* docs(session): fix weird grammar

* docs: update crate docs

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-09 19:19:29 +00:00
asonix
8195484415
actix-ws: take the encoded buffer when yielding rather than split it (#435)
* actix-ws: take the encoded buffer when yielding rather than split it

* actix-ws: add memory reduction to changelog

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-06-09 05:06:24 +00:00
Rob Ede
3ae4ef2706
docs: remove unnecessary code block annotations 2024-06-09 05:18:43 +01:00
dependabot[bot]
65c698cd7f
build(deps): bump taiki-e/install-action from 2.33.34 to 2.34.1 (#441)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.33.34 to 2.34.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.33.34...v2.34.1)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-03 17:38:33 +00:00
dependabot[bot]
f2ef72d056
build(deps): bump taiki-e/install-action from 2.33.26 to 2.33.34 (#440)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.33.26 to 2.33.34.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.33.26...v2.33.34)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 17:09:28 +00:00
dependabot[bot]
e4ee236341
build(deps): bump taiki-e/install-action from 2.33.22 to 2.33.26 (#436)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.33.22 to 2.33.26.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.33.22...v2.33.26)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 17:14:02 +00:00
dependabot[bot]
41ae57d414
build(deps): bump JamesIves/github-pages-deploy-action (#437)
Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases)
- [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.6.0...v4.6.1)

---
updated-dependencies:
- dependency-name: JamesIves/github-pages-deploy-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 17:13:49 +00:00
dependabot[bot]
1b82024499
build(deps): bump codecov/codecov-action from 4.3.1 to 4.4.1 (#438)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.3.1 to 4.4.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.3.1...v4.4.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 17:13:30 +00:00
asonix
6b04450703
Enable sending Continuations from actix-ws (#431)
* Enable sending continuations from an actix-ws Session

* actix-ws: Allow sending continuations from Session

* Convert ignored doctests to no_run doctests

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
2024-05-13 16:49:34 +00:00
dependabot[bot]
c0c7588a57
build(deps): bump taiki-e/install-action from 2.33.17 to 2.33.22 (#432)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 17:26:31 +01:00
dependabot[bot]
b918084a53
build(deps): bump codecov/codecov-action from 4.3.0 to 4.3.1 (#428)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.3.0 to 4.3.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.3.0...v4.3.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 19:39:46 +00:00
dependabot[bot]
b762b41360
build(deps): bump taiki-e/install-action from 2.33.9 to 2.33.17 (#429)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.33.9 to 2.33.17.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.33.9...v2.33.17)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 19:39:34 +00:00
dependabot[bot]
da53492c8c
build(deps): bump codecov/codecov-action from 4.2.0 to 4.3.0 (#421)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.2.0...v4.3.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 03:24:33 +01:00
dependabot[bot]
2c81bc093b
build(deps): bump taiki-e/cache-cargo-install-action from 1.3.0 to 2.0.0 (#426)
Bumps [taiki-e/cache-cargo-install-action](https://github.com/taiki-e/cache-cargo-install-action) from 1.3.0 to 2.0.0.
- [Release notes](https://github.com/taiki-e/cache-cargo-install-action/releases)
- [Changelog](https://github.com/taiki-e/cache-cargo-install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/cache-cargo-install-action/compare/v1.3.0...v2.0.0)

---
updated-dependencies:
- dependency-name: taiki-e/cache-cargo-install-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 03:23:33 +01:00
dependabot[bot]
a2ef65715b
build(deps): bump taiki-e/install-action from 2.32.9 to 2.33.9 (#425)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.32.9 to 2.33.9.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.32.9...v2.33.9)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 03:23:22 +01:00
dependabot[bot]
9beb348d45
build(deps): bump JamesIves/github-pages-deploy-action (#423)
Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases)
- [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.5.0...v4.6.0)

---
updated-dependencies:
- dependency-name: JamesIves/github-pages-deploy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 03:23:14 +01:00
dependabot[bot]
66544952b6
build(deps): bump codecov/codecov-action from 4.1.1 to 4.2.0 (#419)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.1.1 to 4.2.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.1.1...v4.2.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-09 10:13:54 +00:00
dependabot[bot]
9ddb95b74a
build(deps): bump taiki-e/install-action from 2.32.1 to 2.32.9 (#420)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.32.1 to 2.32.9.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.32.1...v2.32.9)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-09 10:13:40 +00:00
dependabot[bot]
f450e3fb85
build(deps): bump taiki-e/install-action from 2.29.7 to 2.32.1 (#417)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.29.7 to 2.32.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.29.7...v2.32.1)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 16:13:48 +00:00
dependabot[bot]
31951dcc9b
build(deps): bump codecov/codecov-action from 4.1.0 to 4.1.1 (#418)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 16:13:30 +00:00
dependabot[bot]
dfc6fe1986
build(deps): bump taiki-e/install-action from 2.29.0 to 2.29.7 (#414)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 17:52:13 +00:00
dependabot[bot]
122fba0580
build(deps): bump taiki-e/install-action from 2.28.11 to 2.29.0 (#413)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 15:04:23 +00:00
Rob Ede
f250348e57
chore: remove actix-redis crate (#408) 2024-03-12 23:30:06 +00:00
dependabot[bot]
e6f99e915d
build(deps): bump taiki-e/install-action from 2.28.1 to 2.28.11 (#411)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.28.1 to 2.28.11.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.28.1...v2.28.11)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-11 15:28:29 +00:00
dependabot[bot]
9d68074bf1
build(deps): bump taiki-e/install-action from 2.27.10 to 2.28.1 (#410)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.27.10 to 2.28.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.27.10...v2.28.1)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-04 16:06:35 +00:00
dependabot[bot]
bbb4ed047c
build(deps): bump codecov/codecov-action from 4.0.2 to 4.1.0 (#409)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.0.2 to 4.1.0.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4.0.2...v4.1.0)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-04 16:06:19 +00:00
Rob Ede
39291c86b7
test: fix doc test 2024-03-02 21:46:36 +00:00
90 changed files with 51634 additions and 1266 deletions

View File

@ -1,10 +1,10 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
- package-ecosystem: cargo
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly

View File

@ -31,12 +31,12 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: nightly
- name: Install cargo-hack and cargo-ci-cache-clean
uses: taiki-e/install-action@v2.27.10
- name: Install cargo-hack, cargo-ci-cache-clean
uses: taiki-e/install-action@v2.49.42
with:
tool: cargo-hack,cargo-ci-cache-clean
@ -71,13 +71,22 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install OpenSSL
if: matrix.target.os == 'windows-latest'
shell: bash
run: |
set -e
choco install openssl --version=1.1.1.2100 -y --no-progress
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: nightly
- name: Install cargo-hack and cargo-ci-cache-clean
uses: taiki-e/install-action@v2.27.10
uses: taiki-e/install-action@v2.49.42
with:
tool: cargo-hack,cargo-ci-cache-clean
@ -92,7 +101,7 @@ jobs:
- name: tests
timeout-minutes: 40
run: cargo ci-test --exclude=actix-redis --exclude=actix-session --exclude=actix-limitation -- --nocapture
run: cargo ci-test --exclude=actix-session --exclude=actix-limitation -- --nocapture
- name: CI cache clean
run: cargo-ci-cache-clean

View File

@ -44,19 +44,18 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack and cargo-ci-cache-clean
uses: taiki-e/install-action@v2.27.10
- name: Install cargo-hack and cargo-ci-cache-clean, just
uses: taiki-e/install-action@v2.49.42
with:
tool: cargo-hack,cargo-ci-cache-clean
tool: cargo-hack,cargo-ci-cache-clean,just
# - name: workaround MSRV issues
# if: matrix.version.name == 'msrv'
# run: |
# cargo update -p=time:0.3.20 --precise=0.3.16
- name: workaround MSRV issues
if: matrix.version.name == 'msrv'
run: just downgrade-for-msrv
- name: check minimal
run: cargo ci-min
@ -92,20 +91,28 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install OpenSSL
if: matrix.target.os == 'windows-latest'
shell: bash
run: |
set -e
choco install openssl --version=1.1.1.2100 -y --no-progress
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
- name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack and cargo-ci-cache-clean
uses: taiki-e/install-action@v2.27.10
- name: Install cargo-hack, cargo-ci-cache-clean, just
uses: taiki-e/install-action@v2.49.42
with:
tool: cargo-hack,cargo-ci-cache-clean
tool: cargo-hack,cargo-ci-cache-clean,just
# - name: workaround MSRV issues
# if: matrix.version.name == 'msrv'
# run: |
# cargo update -p=time:0.3.20 --precise=0.3.16
- name: workaround MSRV issues
if: matrix.version.name == 'msrv'
run: just downgrade-for-msrv
- name: check minimal
run: cargo ci-min
@ -118,22 +125,29 @@ jobs:
- name: tests
timeout-minutes: 40
run: cargo ci-test --exclude=actix-redis --exclude=actix-session --exclude=actix-limitation
run: cargo ci-test --exclude=actix-session --exclude=actix-limitation
- name: CI cache clean
run: cargo-ci-cache-clean
doc_tests:
name: doc tests
name: Documentation Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: nightly
- name: doc tests
timeout-minutes: 40
run: cargo ci-doctest -- --nocapture
- name: Install just
uses: taiki-e/install-action@v2.49.42
with:
tool: just
- name: Test docs
run: just test-docs
- name: Build docs
run: just doc

View File

@ -25,20 +25,21 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: nightly
components: llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2.27.10
- name: Install just, cargo-llvm-cov, cargo-nextest
uses: taiki-e/install-action@v2.49.42
with:
tool: cargo-llvm-cov
tool: just,cargo-llvm-cov,cargo-nextest
- name: Generate code coverage
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
run: just test-coverage-codecov
- name: Upload to Codecov
uses: codecov/codecov-action@v4.0.2
uses: codecov/codecov-action@v5.4.0
with:
files: codecov.json
fail_ci_if_error: true

View File

@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
toolchain: nightly
components: rustfmt
@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
with:
components: clippy
@ -46,32 +46,3 @@ jobs:
clippy_flags: >-
--workspace --all-features --tests --examples --bins --
-A unknown_lints -D clippy::todo -D clippy::dbg_macro
public-api-diff:
runs-on: ubuntu-latest
steps:
- name: checkout ${{ github.base_ref }}
uses: actions/checkout@v4
with:
ref: ${{ github.base_ref }}
- name: checkout ${{ github.head_ref }}
uses: actions/checkout@v4
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
toolchain: nightly
- name: Install cargo-public-api
uses: taiki-e/cache-cargo-install-action@v1.3.0
with:
tool: cargo-public-api
- name: generate API diff
run: |
for f in $(find -mindepth 2 -maxdepth 2 -name Cargo.toml); do
cargo public-api --manifest-path "$f" --all-features diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }} >> /tmp/diff.txt
done
cat /tmp/diff.txt

View File

@ -1,36 +0,0 @@
name: Upload Documentation
on:
push:
branches: [master]
permissions:
contents: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
toolchain: nightly
- name: Build Docs
run: cargo doc --no-deps --workspace --all-features
- name: Tweak HTML
run: echo '<meta http-equiv="refresh" content="0;url=actix_cors/index.html">' > target/doc/index.html
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4.5.0
with:
folder: target/doc
branch: gh-pages

1
.gitignore vendored
View File

@ -1,6 +1,5 @@
/target
**/*.rs.bk
Cargo.lock
guide/build/
/gh-pages

3292
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,6 @@ members = [
"actix-identity",
"actix-limitation",
"actix-protobuf",
"actix-redis",
"actix-session",
"actix-settings",
"actix-web-httpauth",
@ -13,17 +12,22 @@ members = [
]
[workspace.package]
repository = "https://github.com/actix/actix-extras"
homepage = "https://actix.rs"
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.75"
[workspace.lints.rust]
rust-2018-idioms = { level = "deny" }
nonstandard-style = { level = "deny" }
future-incompatible = { level = "deny" }
[patch.crates-io]
actix-cors = { path = "./actix-cors" }
actix-identity = { path = "./actix-identity" }
actix-limitation = { path = "./actix-limitation" }
actix-protobuf = { path = "./actix-protobuf" }
actix-redis = { path = "./actix-redis" }
actix-session = { path = "./actix-session" }
actix-settings = { path = "./actix-settings" }
actix-web-httpauth = { path = "./actix-web-httpauth" }

View File

@ -19,7 +19,6 @@
| [actix-identity] | [![crates.io](https://img.shields.io/crates/v/actix-identity?label=latest)](https://crates.io/crates/actix-identity) [![dependency status](https://deps.rs/crate/actix-identity/latest/status.svg)](https://deps.rs/crate/actix-identity) | Identity management. |
| [actix-limitation] | [![crates.io](https://img.shields.io/crates/v/actix-limitation?label=latest)](https://crates.io/crates/actix-limitation) [![dependency status](https://deps.rs/crate/actix-limitation/latest/status.svg)](https://deps.rs/crate/actix-limitation) | Rate-limiting using a fixed window counter for arbitrary keys, backed by Redis. |
| [actix-protobuf] | [![crates.io](https://img.shields.io/crates/v/actix-protobuf?label=latest)](https://crates.io/crates/actix-protobuf) [![dependency status](https://deps.rs/crate/actix-protobuf/latest/status.svg)](https://deps.rs/crate/actix-protobuf) | Protobuf payload extractor. |
| [actix-redis] | [![crates.io](https://img.shields.io/crates/v/actix-redis?label=latest)](https://crates.io/crates/actix-redis) [![dependency status](https://deps.rs/crate/actix-redis/latest/status.svg)](https://deps.rs/crate/actix-redis) | Actor-based Redis client. |
| [actix-session] | [![crates.io](https://img.shields.io/crates/v/actix-session?label=latest)](https://crates.io/crates/actix-session) [![dependency status](https://deps.rs/crate/actix-session/latest/status.svg)](https://deps.rs/crate/actix-session) | Session management. |
| [actix-settings] | [![crates.io](https://img.shields.io/crates/v/actix-settings?label=latest)](https://crates.io/crates/actix-settings) [![dependency status](https://deps.rs/crate/actix-settings/latest/status.svg)](https://deps.rs/crate/actix-settings) | Easily manage Actix Web's settings from a TOML file and environment variables. |
| [actix-web-httpauth] | [![crates.io](https://img.shields.io/crates/v/actix-web-httpauth?label=latest)](https://crates.io/crates/actix-web-httpauth) [![dependency status](https://deps.rs/crate/actix-web-httpauth/latest/status.svg)](https://deps.rs/crate/actix-web-httpauth) | HTTP authentication schemes. |
@ -34,8 +33,7 @@ These crates are provided by the community.
| Crate | | |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| [actix-web-lab] | [![crates.io](https://img.shields.io/crates/v/actix-web-lab?label=latest)][actix-web-lab] [![dependency status](https://deps.rs/crate/actix-web-lab/latest/status.svg)](https://deps.rs/crate/actix-web-lab) | Experimental extractors, middleware, and other extras for possible inclusion in Actix Web. |
| [actix-multipart-extract] | [![crates.io](https://img.shields.io/crates/v/actix-multipart-extract?label=latest)][actix-multipart-extract] [![dependency status](https://deps.rs/crate/actix-multipart-extract/latest/status.svg)](https://deps.rs/crate/actix-multipart-extract) | Better multipart form support for Actix Web. |
| [actix-form-data] | [![crates.io](https://img.shields.io/crates/v/actix-form-data?label=latest)][actix-form-data] [![dependency status](https://deps.rs/crate/actix-form-data/latest/status.svg)](https://deps.rs/crate/actix-form-data) | Multipart form data from actix multipart streams |
| [actix-form-data] | [![crates.io](https://img.shields.io/crates/v/actix-form-data?label=latest)][actix-form-data] [![dependency status](https://deps.rs/crate/actix-form-data/latest/status.svg)](https://deps.rs/crate/actix-form-data) | Multipart form data from actix multipart streams. |
| [actix-governor] | [![crates.io](https://img.shields.io/crates/v/actix-governor?label=latest)][actix-governor] [![dependency status](https://deps.rs/crate/actix-governor/latest/status.svg)](https://deps.rs/crate/actix-governor) | Rate-limiting backed by governor. |
| [actix-casbin] | [![crates.io](https://img.shields.io/crates/v/actix-casbin?label=latest)][actix-casbin] [![dependency status](https://deps.rs/crate/actix-casbin/latest/status.svg)](https://deps.rs/crate/actix-casbin) | Authorization library that supports access control models like ACL, RBAC & ABAC. |
| [actix-ip-filter] | [![crates.io](https://img.shields.io/crates/v/actix-ip-filter?label=latest)][actix-ip-filter] [![dependency status](https://deps.rs/crate/actix-ip-filter/latest/status.svg)](https://deps.rs/crate/actix-ip-filter) | IP address filter. Supports glob patterns. |
@ -46,10 +44,13 @@ These crates are provided by the community.
| [awmp] | [![crates.io](https://img.shields.io/crates/v/awmp?label=latest)][awmp] [![dependency status](https://deps.rs/crate/awmp/latest/status.svg)](https://deps.rs/crate/awmp) | An easy to use wrapper around multipart fields for Actix Web. |
| [tracing-actix-web] | [![crates.io](https://img.shields.io/crates/v/tracing-actix-web?label=latest)][tracing-actix-web] [![dependency status](https://deps.rs/crate/tracing-actix-web/latest/status.svg)](https://deps.rs/crate/tracing-actix-web) | A middleware to collect telemetry data from applications built on top of the Actix Web framework. |
| [actix-hash] | [![crates.io](https://img.shields.io/crates/v/actix-hash?label=latest)][actix-hash] [![dependency status](https://deps.rs/crate/actix-hash/latest/status.svg)](https://deps.rs/crate/actix-hash) | Hashing utilities for Actix Web. |
| [actix-bincode] | ![crates.io](https://img.shields.io/crates/v/actix-bincode?label=latest) [![dependency status](https://deps.rs/crate/actix-bincode/latest/status.svg)](https://deps.rs/crate/actix-bincode) | Bincode payload extractor for Actix Web |
| [sentinel-actix] | ![crates.io](https://img.shields.io/crates/v/sentinel-actix?label=latest) [![dependency status](https://deps.rs/crate/sentinel-actix/latest/status.svg)](https://deps.rs/crate/sentinel-actix) | General and flexible protection for Actix Web |
| [actix-bincode] | ![crates.io](https://img.shields.io/crates/v/actix-bincode?label=latest) [![dependency status](https://deps.rs/crate/actix-bincode/latest/status.svg)](https://deps.rs/crate/actix-bincode) | Bincode payload extractor for Actix Web. |
| [sentinel-actix] | ![crates.io](https://img.shields.io/crates/v/sentinel-actix?label=latest) [![dependency status](https://deps.rs/crate/sentinel-actix/latest/status.svg)](https://deps.rs/crate/sentinel-actix) | General and flexible protection for Actix Web. |
| [actix-telepathy] | ![crates.io](https://img.shields.io/crates/v/actix-telepathy?label=latest) [![dependency status](https://deps.rs/crate/actix-telepathy/latest/status.svg)](https://deps.rs/crate/actix-telepathy) | Build distributed applications with `RemoteActors` and `RemoteMessages`. |
| [apistos] | ![crates.io](https://img.shields.io/crates/v/apistos?label=latest) [![dependency status](https://deps.rs/crate/apistos/latest/status.svg)](https://deps.rs/crate/apistos) | Automatic OpenAPI v3 documentation for Actix Web |
| [apistos] | ![crates.io](https://img.shields.io/crates/v/apistos?label=latest) [![dependency status](https://deps.rs/crate/apistos/latest/status.svg)](https://deps.rs/crate/apistos) | Automatic OpenAPI v3 documentation for Actix Web. |
| [actix-web-validation] | ![crates.io](https://img.shields.io/crates/v/actix-web-validation?label=latest) [![dependency status](https://deps.rs/crate/actix-web-validation/latest/status.svg)](https://deps.rs/crate/actix-web-validation) | Request validation for Actix Web. |
| [actix-jwt-cookies] | ![crates.io](https://img.shields.io/crates/v/actix-jwt-cookies?label=latest) [![dependency status](https://deps.rs/repo/github/Necoo33/actix-jwt-cookies/status.svg)](https://deps.rs/repo/github/Necoo33/actix-jwt-cookies?path=%2F) | Store your data in encrypted cookies and get it elegantly. |
| [actix-ws-broadcaster] | ![crates.io](https://img.shields.io/crates/v/actix-ws-broadcaster?label=latest) [![dependency status](https://deps.rs/repo/github/Necoo33/actix-ws-broadcaster/status.svg?path=%2F)](https://deps.rs/repo/github/Necoo33/actix-ws-broadcaster?path=%2F) | A broadcaster library for actix-ws that includes grouping and conditional broadcasting. |
To add a crate to this list, submit a pull request.
@ -62,7 +63,6 @@ To add a crate to this list, submit a pull request.
[actix-identity]: ./actix-identity
[actix-limitation]: ./actix-limitation
[actix-protobuf]: ./actix-protobuf
[actix-redis]: ./actix-redis
[actix-session]: ./actix-session
[actix-settings]: ./actix-settings
[actix-web-httpauth]: ./actix-web-httpauth
@ -82,5 +82,9 @@ To add a crate to this list, submit a pull request.
[actix-hash]: https://crates.io/crates/actix-hash
[actix-bincode]: https://crates.io/crates/actix-bincode
[sentinel-actix]: https://crates.io/crates/sentinel-actix
[actix-telepathy]: https://github.com/wenig/actix-telepathy
[apistos]: https://github.com/netwo-io/apistos
[actix-telepathy]: https://crates.io/crates/actix-telepathy
[actix-web-validation]: https://crates.io/crates/actix-web-validation
[actix-telepathy]: https://crates.io/crates/actix-telepathy
[apistos]: https://crates.io/crates/apistos
[actix-jwt-cookies]: https://crates.io/crates/actix-jwt-cookies
[actix-ws-broadcaster]: https://crates.io/crates/actix-ws-broadcaster

View File

@ -2,6 +2,10 @@
## Unreleased
## 0.7.1
- Implement `PartialEq` for `Cors` allowing for better testing.
## 0.7.0
- `Cors` is now marked `#[must_use]`.

View File

@ -1,14 +1,14 @@
[package]
name = "actix-cors"
version = "0.7.0"
version = "0.7.1"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
]
description = "Cross-Origin Resource Sharing (CORS) controls for Actix Web"
keywords = ["actix", "cors", "web", "security", "crossorigin"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras/tree/master/actix-cors"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
@ -24,7 +24,7 @@ draft-private-network-access = []
actix-utils = "3"
actix-web = { version = "4", default-features = false }
derive_more = "0.99.7"
derive_more = { version = "2", features = ["display", "error"] }
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
log = "0.4"
once_cell = "1"
@ -34,3 +34,6 @@ smallvec = "1"
actix-web = { version = "4", default-features = false, features = ["macros"] }
env_logger = "0.11"
regex = "1.4"
[lints]
workspace = true

View File

@ -3,11 +3,11 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-cors?label=latest)](https://crates.io/crates/actix-cors)
[![Documentation](https://docs.rs/actix-cors/badge.svg?version=0.7.0)](https://docs.rs/actix-cors/0.7.0)
[![Documentation](https://docs.rs/actix-cors/badge.svg?version=0.7.1)](https://docs.rs/actix-cors/0.7.1)
![Version](https://img.shields.io/badge/rustc-1.75+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-cors.svg)
<br />
[![Dependency Status](https://deps.rs/crate/actix-cors/0.7.0/status.svg)](https://deps.rs/crate/actix-cors/0.7.0)
[![Dependency Status](https://deps.rs/crate/actix-cors/0.7.1/status.svg)](https://deps.rs/crate/actix-cors/0.7.1)
[![Download](https://img.shields.io/crates/d/actix-cors.svg)](https://crates.io/crates/actix-cors)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@ -214,7 +214,7 @@ impl Cors {
/// See [`Cors::allowed_methods`] for more info on allowed methods.
pub fn allow_any_method(mut self) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.allowed_methods = ALL_METHODS_SET.clone();
ALL_METHODS_SET.clone_into(&mut cors.allowed_methods);
}
self
@ -608,6 +608,19 @@ where
.unwrap()
}
impl PartialEq for Cors {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
// Because of the cors-function, checking if the content is equal implies that the errors are equal
//
// Proof by contradiction:
// Lets assume that the inner values are equal, but the error values are not.
// This means there had been an error, which has been fixed.
// This cannot happen as the first call to set the invalid value means that further usages of the cors-function will reject other input.
// => inner has to be in a different state
}
}
#[cfg(test)]
mod test {
use std::convert::Infallible;
@ -679,4 +692,11 @@ mod test {
Cors::default().new_transform(srv).await.unwrap();
}
#[test]
fn impl_eq() {
assert_eq!(Cors::default(), Cors::default());
assert_ne!(Cors::default().send_wildcard(), Cors::default());
assert_ne!(Cors::default(), Cors::permissive());
}
}

View File

@ -1,40 +1,40 @@
use actix_web::{http::StatusCode, HttpResponse, ResponseError};
use derive_more::{Display, Error};
use derive_more::derive::{Display, Error};
/// Errors that can occur when processing CORS guarded requests.
#[derive(Debug, Clone, Display, Error)]
#[non_exhaustive]
pub enum CorsError {
/// Allowed origin argument must not be wildcard (`*`).
#[display(fmt = "`allowed_origin` argument must not be wildcard (`*`)")]
#[display("`allowed_origin` argument must not be wildcard (`*`)")]
WildcardOrigin,
/// Request header `Origin` is required but was not provided.
#[display(fmt = "Request header `Origin` is required but was not provided")]
#[display("Request header `Origin` is required but was not provided")]
MissingOrigin,
/// Request header `Access-Control-Request-Method` is required but is missing.
#[display(fmt = "Request header `Access-Control-Request-Method` is required but is missing")]
#[display("Request header `Access-Control-Request-Method` is required but is missing")]
MissingRequestMethod,
/// Request header `Access-Control-Request-Method` has an invalid value.
#[display(fmt = "Request header `Access-Control-Request-Method` has an invalid value")]
#[display("Request header `Access-Control-Request-Method` has an invalid value")]
BadRequestMethod,
/// Request header `Access-Control-Request-Headers` has an invalid value.
#[display(fmt = "Request header `Access-Control-Request-Headers` has an invalid value")]
#[display("Request header `Access-Control-Request-Headers` has an invalid value")]
BadRequestHeaders,
/// Origin is not allowed to make this request.
#[display(fmt = "Origin is not allowed to make this request")]
#[display("Origin is not allowed to make this request")]
OriginNotAllowed,
/// Request method is not allowed.
#[display(fmt = "Requested method is not allowed")]
#[display("Requested method is not allowed")]
MethodNotAllowed,
/// One or more request headers are not allowed.
#[display(fmt = "One or more request headers are not allowed")]
#[display("One or more request headers are not allowed")]
HeadersNotAllowed,
}

View File

@ -27,6 +27,12 @@ impl Default for OriginFn {
}
}
impl PartialEq for OriginFn {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.boxed_fn, &other.boxed_fn)
}
}
impl fmt::Debug for OriginFn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("origin_fn")
@ -40,7 +46,7 @@ pub(crate) fn header_value_try_into_method(hdr: &HeaderValue) -> Option<Method>
.and_then(|meth| Method::try_from(meth).ok())
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Inner {
pub(crate) allowed_origins: AllOrSome<HashSet<HeaderValue>>,
pub(crate) allowed_origins_fns: SmallVec<[OriginFn; 4]>,

View File

@ -49,7 +49,6 @@
//! [Private Network Access]: https://wicg.github.io/private-network-access
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]

View File

@ -2,6 +2,10 @@
## Unreleased
## 0.8.0
- Update `actix-session` dependency to `0.10`.
## 0.7.1
- Add `IdentityMiddlewareBuilder::{id_key, last_visit_unix_timestamp_key, login_unix_timestamp_key}()` methods for customizing keys used in session. Defaults remain the same as before.

View File

@ -1,14 +1,14 @@
[package]
name = "actix-identity"
version = "0.7.1"
version = "0.8.0"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Luca Palmieri <rust@lpalmieri.com>",
]
description = "Identity management for Actix Web"
keywords = ["actix", "auth", "identity", "web", "security"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
@ -19,11 +19,11 @@ all-features = true
[dependencies]
actix-service = "2"
actix-session = "0.9"
actix-session = "0.10"
actix-utils = "3"
actix-web = { version = "4", default-features = false, features = ["cookies", "secure-cookies"] }
derive_more = "0.99.7"
derive_more = { version = "2", features = ["display", "error", "from"] }
futures-core = "0.3.17"
serde = { version = "1", features = ["derive"] }
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
@ -31,8 +31,11 @@ tracing = { version = "0.1.30", default-features = false, features = ["log"] }
[dev-dependencies]
actix-http = "3"
actix-web = { version = "4", default-features = false, features = ["macros", "cookies", "secure-cookies"] }
actix-session = { version = "0.9", features = ["redis-rs-session", "cookie-session"] }
actix-session = { version = "0.10", features = ["redis-session", "cookie-session"] }
env_logger = "0.11"
reqwest = { version = "0.11", default-features = false, features = ["cookies", "json"] }
reqwest = { version = "0.12", default-features = false, features = ["cookies", "json"] }
uuid = { version = "1", features = ["v4"] }
[lints]
workspace = true

View File

@ -5,9 +5,9 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-identity?label=latest)](https://crates.io/crates/actix-identity)
[![Documentation](https://docs.rs/actix-identity/badge.svg?version=0.7.1)](https://docs.rs/actix-identity/0.7.1)
[![Documentation](https://docs.rs/actix-identity/badge.svg?version=0.8.0)](https://docs.rs/actix-identity/0.8.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-identity)
[![Dependency Status](https://deps.rs/crate/actix-identity/0.7.1/status.svg)](https://deps.rs/crate/actix-identity/0.7.1)
[![Dependency Status](https://deps.rs/crate/actix-identity/0.8.0/status.svg)](https://deps.rs/crate/actix-identity/0.8.0)
<!-- prettier-ignore-end -->
@ -83,8 +83,10 @@ async fn login(request: HttpRequest) -> impl Responder {
}
#[post("/logout")]
async fn logout(user: Identity) -> impl Responder {
async fn logout(user: Option<Identity>) -> impl Responder {
if let Some(user) = user {
user.logout();
}
HttpResponse::Ok()
}
```

View File

@ -13,10 +13,10 @@
//! http -v --session=identity GET localhost:8080/
//! ```
use std::io;
use std::{io, time::Duration};
use actix_identity::{Identity, IdentityMiddleware};
use actix_session::{storage::CookieSessionStore, SessionMiddleware};
use actix_session::{config::PersistentSession, storage::CookieSessionStore, SessionMiddleware};
use actix_web::{
cookie::Key, get, middleware::Logger, post, App, HttpMessage, HttpRequest, HttpResponse,
HttpServer, Responder,
@ -28,16 +28,25 @@ async fn main() -> io::Result<()> {
let secret_key = Key::generate();
let expiration = Duration::from_secs(24 * 60 * 60);
HttpServer::new(move || {
let session_mw =
SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone())
// disable secure cookie for local testing
.cookie_secure(false)
// Set a ttl for the cookie if the identity should live longer than the user session
.session_lifecycle(
PersistentSession::default().session_ttl(expiration.try_into().unwrap()),
)
.build();
let identity_mw = IdentityMiddleware::builder()
.visit_deadline(Some(expiration))
.build();
App::new()
// Install the identity framework first.
.wrap(IdentityMiddleware::default())
.wrap(identity_mw)
// The identity system is built on top of sessions. You must install the session
// middleware to leverage `actix-identity`. The session middleware must be mounted
// AFTER the identity middleware: `actix-web` invokes middleware in the OPPOSITE

View File

@ -2,11 +2,11 @@
use actix_session::{SessionGetError, SessionInsertError};
use actix_web::{cookie::time::error::ComponentRange, http::StatusCode, ResponseError};
use derive_more::{Display, Error, From};
use derive_more::derive::{Display, Error, From};
/// Error that can occur during login attempts.
#[derive(Debug, Display, Error, From)]
#[display(fmt = "{_0}")]
#[display("{_0}")]
pub struct LoginError(SessionInsertError);
impl ResponseError for LoginError {
@ -17,7 +17,7 @@ impl ResponseError for LoginError {
/// Error encountered when working with a session that has expired.
#[derive(Debug, Display, Error)]
#[display(fmt = "The given session has expired and is no longer valid")]
#[display("The given session has expired and is no longer valid")]
pub struct SessionExpiryError(#[error(not(source))] pub(crate) ComponentRange);
/// The identity information has been lost.
@ -25,7 +25,7 @@ pub struct SessionExpiryError(#[error(not(source))] pub(crate) ComponentRange);
/// Seeing this error in user code indicates a bug in actix-identity.
#[derive(Debug, Display, Error)]
#[display(
fmt = "The identity information in the current session has disappeared after having been \
"The identity information in the current session has disappeared after having been \
successfully validated. This is likely to be a bug."
)]
#[non_exhaustive]
@ -33,7 +33,7 @@ pub struct LostIdentityError;
/// There is no identity information attached to the current session.
#[derive(Debug, Display, Error)]
#[display(fmt = "There is no identity information attached to the current session")]
#[display("There is no identity information attached to the current session")]
#[non_exhaustive]
pub struct MissingIdentityError;
@ -42,21 +42,21 @@ pub struct MissingIdentityError;
#[non_exhaustive]
pub enum GetIdentityError {
/// The session has expired.
#[display(fmt = "{_0}")]
#[display("{_0}")]
SessionExpiryError(SessionExpiryError),
/// No identity is found in a session.
#[display(fmt = "{_0}")]
#[display("{_0}")]
MissingIdentityError(MissingIdentityError),
/// Failed to accessing the session store.
#[display(fmt = "{_0}")]
#[display("{_0}")]
SessionGetError(SessionGetError),
/// Identity info was lost after being validated.
///
/// Seeing this error indicates a bug in actix-identity.
#[display(fmt = "{_0}")]
#[display("{_0}")]
LostIdentityError(LostIdentityError),
}

View File

@ -20,7 +20,7 @@ impl IdentityExt for ServiceRequest {
}
}
impl<'a> IdentityExt for GuardContext<'a> {
impl IdentityExt for GuardContext<'_> {
fn get_identity(&self) -> Result<Identity, GetIdentityError> {
Identity::extract(&self.req_data())
}

View File

@ -74,8 +74,10 @@ async fn login(request: HttpRequest) -> impl Responder {
}
#[post("/logout")]
async fn logout(user: Identity) -> impl Responder {
async fn logout(user: Option<Identity>) -> impl Responder {
if let Some(user) = user {
user.logout();
}
HttpResponse::Ok()
}
```
@ -93,8 +95,7 @@ In particular, you can automatically log out users who:
*/
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style, missing_docs)]
#![warn(future_incompatible)]
#![deny(missing_docs)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

View File

@ -1,7 +1,7 @@
use std::time::Duration;
use actix_identity::{config::LogoutBehaviour, IdentityMiddleware};
use actix_web::http::StatusCode;
use reqwest::StatusCode;
use crate::{fixtures::user_id, test_app::TestApp};

View File

@ -2,6 +2,9 @@
## Unreleased
- Update `redis` dependency to `0.29`.
- Update `actix-session` dependency to `0.9`.
## 0.5.1
- No significant changes since `0.5.0`.

View File

@ -8,7 +8,7 @@ authors = [
description = "Rate limiter using a fixed window counter for arbitrary keys, backed by Redis for Actix Web"
keywords = ["actix-web", "rate-api", "rate-limit", "limitation"]
categories = ["asynchronous", "web-programming"]
repository = "https://github.com/actix/actix-extras.git"
repository = "https://github.com/actix/actix-extras"
license.workspace = true
edition.workspace = true
rust-version.workspace = true
@ -26,15 +26,18 @@ actix-utils = "3"
actix-web = { version = "4", default-features = false, features = ["cookies"] }
chrono = "0.4"
derive_more = "0.99.7"
derive_more = { version = "2", features = ["display", "error", "from"] }
log = "0.4"
redis = { version = "0.24", default-features = false, features = ["tokio-comp"] }
redis = { version = "0.29", default-features = false, features = ["tokio-comp"] }
time = "0.3"
# session
actix-session = { version = "0.9", optional = true }
actix-session = { version = "0.10", optional = true }
[dev-dependencies]
actix-web = "4"
static_assertions = "1"
uuid = { version = "1", features = ["v4"] }
[lints]
workspace = true

View File

@ -1,4 +1,4 @@
use derive_more::{Display, Error, From};
use derive_more::derive::{Display, Error, From};
use crate::status::Status;
@ -6,20 +6,20 @@ use crate::status::Status;
#[derive(Debug, Display, Error, From)]
pub enum Error {
/// Redis client failed to connect or run a query.
#[display(fmt = "Redis client failed to connect or run a query")]
#[display("Redis client failed to connect or run a query")]
Client(redis::RedisError),
/// Limit is exceeded for a key.
#[display(fmt = "Limit is exceeded for a key")]
#[display("Limit is exceeded for a key")]
#[from(ignore)]
LimitExceeded(#[error(not(source))] Status),
/// Time conversion failed.
#[display(fmt = "Time conversion failed")]
#[display("Time conversion failed")]
Time(time::error::ComponentRange),
/// Generic error.
#[display(fmt = "Generic error")]
#[display("Generic error")]
#[from(ignore)]
Other(#[error(not(source))] String),
}

View File

@ -45,8 +45,7 @@
//! ```
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
#![warn(missing_docs, missing_debug_implementations)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
@ -138,7 +137,7 @@ impl Limiter {
let key = key.into();
let expires = self.period.as_secs();
let mut connection = self.client.get_tokio_connection().await?;
let mut connection = self.client.get_multiplexed_tokio_connection().await?;
// The seed of this approach is outlined Atul R in a blog post about rate limiting using
// NodeJS and Redis. For more details, see https://blog.atulr.com/rate-limiter

View File

@ -16,7 +16,7 @@ impl Status {
/// Constructs status limit status from parts.
#[must_use]
pub(crate) fn new(count: usize, limit: usize, reset_epoch_utc: usize) -> Self {
let remaining = if count >= limit { 0 } else { limit - count };
let remaining = limit.saturating_sub(count);
Status {
limit,

View File

@ -2,6 +2,9 @@
## Unreleased
## 0.11.0
- Updated `prost` dependency to `0.13`.
- Minimum supported Rust version (MSRV) is now 1.75.
## 0.10.0

View File

@ -1,14 +1,14 @@
[package]
name = "actix-protobuf"
version = "0.10.0"
version = "0.11.0"
authors = [
"kingxsp <jin.hb.zh@outlook.com>",
"Yuki Okushi <huyuumi.dev@gmail.com>",
]
description = "Protobuf payload extractor for Actix Web"
keywords = ["actix", "web", "protobuf", "protocol", "rpc"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
@ -19,10 +19,13 @@ all-features = true
[dependencies]
actix-web = { version = "4", default-features = false }
derive_more = "0.99.7"
derive_more = { version = "2", features = ["display"] }
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
prost = { version = "0.12", default-features = false }
prost = { version = "0.13", default-features = false }
[dev-dependencies]
actix-web = { version = "4", default-features = false, features = ["macros"] }
prost = { version = "0.12", default-features = false, features = ["prost-derive"] }
prost = { version = "0.13", default-features = false, features = ["prost-derive"] }
[lints]
workspace = true

View File

@ -5,9 +5,9 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-protobuf?label=latest)](https://crates.io/crates/actix-protobuf)
[![Documentation](https://docs.rs/actix-protobuf/badge.svg?version=0.10.0)](https://docs.rs/actix-protobuf/0.10.0)
[![Documentation](https://docs.rs/actix-protobuf/badge.svg?version=0.11.0)](https://docs.rs/actix-protobuf/0.11.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-protobuf)
[![Dependency Status](https://deps.rs/crate/actix-protobuf/0.10.0/status.svg)](https://deps.rs/crate/actix-protobuf/0.10.0)
[![Dependency Status](https://deps.rs/crate/actix-protobuf/0.11.0/status.svg)](https://deps.rs/crate/actix-protobuf/0.11.0)
<!-- prettier-ignore-end -->

View File

@ -1,8 +1,6 @@
//! Protobuf payload extractor for Actix Web.
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
@ -24,7 +22,7 @@ use actix_web::{
Error, FromRequest, HttpMessage, HttpRequest, HttpResponse, HttpResponseBuilder, Responder,
ResponseError,
};
use derive_more::Display;
use derive_more::derive::Display;
use futures_util::{
future::{FutureExt as _, LocalBoxFuture},
stream::StreamExt as _,
@ -34,26 +32,28 @@ use prost::{DecodeError as ProtoBufDecodeError, EncodeError as ProtoBufEncodeErr
#[derive(Debug, Display)]
pub enum ProtoBufPayloadError {
/// Payload size is bigger than 256k
#[display(fmt = "Payload size is bigger than 256k")]
#[display("Payload size is bigger than 256k")]
Overflow,
/// Content type error
#[display(fmt = "Content type error")]
#[display("Content type error")]
ContentType,
/// Serialize error
#[display(fmt = "ProtoBuf serialize error: {_0}")]
#[display("ProtoBuf serialize error: {_0}")]
Serialize(ProtoBufEncodeError),
/// Deserialize error
#[display(fmt = "ProtoBuf deserialize error: {_0}")]
#[display("ProtoBuf deserialize error: {_0}")]
Deserialize(ProtoBufDecodeError),
/// Payload error
#[display(fmt = "Error that occur during reading payload: {_0}")]
#[display("Error that occur during reading payload: {_0}")]
Payload(PayloadError),
}
// TODO: impl error for ProtoBufPayloadError
impl ResponseError for ProtoBufPayloadError {
fn error_response(&self) -> HttpResponse {
match *self {

View File

@ -1,154 +0,0 @@
# Changes
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.75.
## 0.13.0
- Update `redis-async` dependency to `0.16`.
- Minimum supported Rust version (MSRV) is now 1.68.
## 0.12.0
- Update `actix` dependency to `0.13`.
- Update `redis-async` dependency to `0.13`.
- Update `tokio-util` dependency to `0.7`.
- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency.
## 0.11.0
### Removed
- `RedisSession` has been removed. Check out `RedisActorSessionStore` in `actix-session` for a session store backed by Redis using `actix-redis`. [#212]
### Changed
- Update `redis-async` dependency to `0.12`. [#212]
[#212]: https://github.com/actix/actix-extras/pull/212
## 0.10.0
- Update `actix-web` dependency to `4`.
## 0.10.0-beta.6
- Update `actix-web` dependency to `4.0.0-rc.1`.
## 0.10.0-beta.5
- Update `actix-web` dependency to `4.0.0.beta-18`. [#218]
- Minimum supported Rust version (MSRV) is now 1.54.
[#218]: https://github.com/actix/actix-extras/pull/218
## 0.10.0-beta.4
- A session will be created in Redis if and only if there is some data inside the session state. This reduces the performance impact of `RedisSession` on routes that do not leverage sessions. [#207]
- Update `actix-web` dependency to `4.0.0.beta-14`. [#209]
[#207]: https://github.com/actix/actix-extras/pull/207
[#209]: https://github.com/actix/actix-extras/pull/209
## 0.10.0-beta.3
- Update `actix-web` dependency to v4.0.0-beta.10. [#203]
- Minimum supported Rust version (MSRV) is now 1.52.
[#203]: https://github.com/actix/actix-extras/pull/203
## 0.10.0-beta.2
- No notable changes.
## 0.10.0-beta.1
- Update `actix-web` dependency to 4.0.0 beta.
- Minimum supported Rust version (MSRV) is now 1.46.0.
## 0.9.2
- Implement `std::error::Error` for `Error` [#135]
- Allow the removal of `Max-Age` for session-only cookies. [#161]
[#135]: https://github.com/actix/actix-extras/pull/135
[#161]: https://github.com/actix/actix-extras/pull/161
## 0.9.1
- Enforce minimum redis-async version of 0.6.3 to workaround breaking patch change.
## 0.9.0
- Update `actix-web` dependency to 3.0.0.
- Minimize `futures` dependency.
## 0.9.0-alpha.2
- Add `cookie_http_only` functionality to RedisSession builder, setting this to false allows JavaScript to access cookies. Defaults to true.
- Change type of parameter of ttl method to u32.
- Update `actix` to 0.10.0-alpha.3
- Update `tokio-util` to 0.3
- Minimum supported Rust version(MSRV) is now 1.40.0.
## 0.9.0-alpha.1
- Update `actix` to 0.10.0-alpha.2
- Update `actix-session` to 0.4.0-alpha.1
- Update `actix-web` to 3.0.0-alpha.1
- Update `time` to 0.2.9
## 0.8.1
- Move `env_logger` dependency to dev-dependencies and update to 0.7
- Update `actix_web` to 2.0.0 from 2.0.0-rc
- Move repository to actix-extras
## 0.8.0 - 2019-12-20
- Release
## 0.8.0-alpha.1 - 2019-12-16
- Migrate to actix 0.9
## 0.7.0 - 2019-09-25
- added cache_keygen functionality to RedisSession builder, enabling support for customizable cache key creation
## 0.6.1 - 2019-07-19
- remove ClonableService usage
- added comprehensive tests for session workflow
## 0.6.0 - 2019-07-08
- actix-web 1.0.0 compatibility
- Upgraded logic that evaluates session state, including new SessionStatus field, and introduced `session.renew()` and `session.purge()` functionality. Use `renew()` to cycle the session key at successful login. `renew()` keeps a session's state while replacing the old cookie and session key with new ones. Use `purge()` at logout to invalidate the session cookie and remove the session's redis cache entry.
## 0.5.1 - 2018-08-02
- Use cookie 0.11
## 0.5.0 - 2018-07-21
- Session cookie configuration
- Actix/Actix-web 0.7 compatibility
## 0.4.0 - 2018-05-08
- Actix web 0.6 compatibility
## 0.3.0 - 2018-04-10
- Actix web 0.5 compatibility
## 0.2.0 - 2018-02-28
- Use resolver actor from actix
- Use actix web 0.5
## 0.1.0 - 2018-01-23
- First release

View File

@ -1,48 +0,0 @@
[package]
name = "actix-redis"
version = "0.13.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actor-based Redis client"
keywords = ["actix", "redis", "async"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git"
categories = ["network-programming", "asynchronous"]
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
[lib]
name = "actix_redis"
path = "src/lib.rs"
[features]
default = ["web"]
# actix-web integration
web = ["actix-web"]
[dependencies]
actix = { version = "0.13", default-features = false }
actix-rt = { version = "2.1", default-features = false }
actix-service = "2"
actix-tls = { version = "3", default-features = false, features = ["connect"] }
log = "0.4.6"
backoff = "0.4.0"
derive_more = "0.99.7"
futures-core = "0.3.17"
redis-async = "0.16"
time = "0.3"
tokio = { version = "1.18.4", features = ["sync"] }
tokio-util = "0.7"
actix-web = { version = "4", default-features = false, optional = true }
[dev-dependencies]
actix-test = "0.1.0-beta.12"
actix-web = { version = "4", default-features = false, features = ["macros"] }
env_logger = "0.11"
serde = { version = "1.0.101", features = ["derive"] }

View File

@ -1 +0,0 @@
../LICENSE-APACHE

View File

@ -1 +0,0 @@
../LICENSE-MIT

View File

@ -1,18 +0,0 @@
# actix-redis
> Actor-based Redis client.
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-redis?label=latest)](https://crates.io/crates/actix-redis)
[![Documentation](https://docs.rs/actix-redis/badge.svg?version=0.13.0)](https://docs.rs/actix-redis/0.13.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-redis)
[![Dependency Status](https://deps.rs/crate/actix-redis/0.13.0/status.svg)](https://deps.rs/crate/actix-redis/0.13.0)
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-redis)
- [Example Project](https://github.com/actix/examples/tree/master/auth/redis-session)
- Minimum Supported Rust Version (MSRV): 1.57

View File

@ -1,32 +0,0 @@
//! Redis integration for `actix`.
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use derive_more::{Display, Error, From};
pub use redis_async::{error::Error as RespError, resp::RespValue, resp_array};
mod redis;
pub use self::redis::{Command, RedisActor};
/// General purpose `actix-redis` error.
#[derive(Debug, Display, Error, From)]
pub enum Error {
#[display(fmt = "Redis error: {_0}")]
Redis(redis_async::error::Error),
/// Receiving message during reconnecting.
#[display(fmt = "Redis: Not connected")]
NotConnected,
/// Cancel all waiters when connection is dropped.
#[display(fmt = "Redis: Disconnected")]
Disconnected,
}
#[cfg(feature = "web")]
impl actix_web::ResponseError for Error {}

View File

@ -1,143 +0,0 @@
use std::{collections::VecDeque, io};
use actix::prelude::*;
use actix_rt::net::TcpStream;
use actix_service::boxed::{self, BoxService};
use actix_tls::connect::{ConnectError, ConnectInfo, Connection, ConnectorService};
use backoff::{backoff::Backoff, ExponentialBackoff};
use log::{error, info, warn};
use redis_async::{
error::Error as RespError,
resp::{RespCodec, RespValue},
};
use tokio::{
io::{split, WriteHalf},
sync::oneshot,
};
use tokio_util::codec::FramedRead;
use crate::Error;
/// Command for sending data to Redis.
#[derive(Debug)]
pub struct Command(pub RespValue);
impl Message for Command {
type Result = Result<RespValue, Error>;
}
/// Redis communication actor.
pub struct RedisActor {
addr: String,
connector: BoxService<ConnectInfo<String>, Connection<String, TcpStream>, ConnectError>,
backoff: ExponentialBackoff,
cell: Option<actix::io::FramedWrite<RespValue, WriteHalf<TcpStream>, RespCodec>>,
queue: VecDeque<oneshot::Sender<Result<RespValue, Error>>>,
}
impl RedisActor {
/// Start new `Supervisor` with `RedisActor`.
pub fn start<S: Into<String>>(addr: S) -> Addr<RedisActor> {
let addr = addr.into();
let backoff = ExponentialBackoff {
max_elapsed_time: None,
..Default::default()
};
Supervisor::start(|_| RedisActor {
addr,
connector: boxed::service(ConnectorService::default()),
cell: None,
backoff,
queue: VecDeque::new(),
})
}
}
impl Actor for RedisActor {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Context<Self>) {
let req = ConnectInfo::new(self.addr.to_owned());
self.connector
.call(req)
.into_actor(self)
.map(|res, act, ctx| match res {
Ok(conn) => {
let stream = conn.into_parts().0;
info!("Connected to redis server: {}", act.addr);
let (r, w) = split(stream);
// configure write side of the connection
let framed = actix::io::FramedWrite::new(w, RespCodec, ctx);
act.cell = Some(framed);
// read side of the connection
ctx.add_stream(FramedRead::new(r, RespCodec));
act.backoff.reset();
}
Err(err) => {
error!("Can not connect to redis server: {}", err);
// re-connect with backoff time.
// we stop current context, supervisor will restart it.
if let Some(timeout) = act.backoff.next_backoff() {
ctx.run_later(timeout, |_, ctx| ctx.stop());
}
}
})
.wait(ctx);
}
}
impl Supervised for RedisActor {
fn restarting(&mut self, _: &mut Self::Context) {
self.cell.take();
for tx in self.queue.drain(..) {
let _ = tx.send(Err(Error::Disconnected));
}
}
}
impl actix::io::WriteHandler<io::Error> for RedisActor {
fn error(&mut self, err: io::Error, _: &mut Self::Context) -> Running {
warn!("Redis connection dropped: {} error: {}", self.addr, err);
Running::Stop
}
}
impl StreamHandler<Result<RespValue, RespError>> for RedisActor {
fn handle(&mut self, msg: Result<RespValue, RespError>, ctx: &mut Self::Context) {
match msg {
Err(e) => {
if let Some(tx) = self.queue.pop_front() {
let _ = tx.send(Err(e.into()));
}
ctx.stop();
}
Ok(val) => {
if let Some(tx) = self.queue.pop_front() {
let _ = tx.send(Ok(val));
}
}
}
}
}
impl Handler<Command> for RedisActor {
type Result = ResponseFuture<Result<RespValue, Error>>;
fn handle(&mut self, msg: Command, _: &mut Self::Context) -> Self::Result {
let (tx, rx) = oneshot::channel();
if let Some(ref mut cell) = self.cell {
self.queue.push_back(tx);
cell.write(msg.0);
} else {
let _ = tx.send(Err(Error::NotConnected));
}
Box::pin(async move { rx.await.map_err(|_| Error::Disconnected)? })
}
}

View File

@ -1,42 +0,0 @@
#[macro_use]
extern crate redis_async;
use actix_redis::{Command, Error, RedisActor, RespValue};
#[actix_web::test]
async fn test_error_connect() {
let addr = RedisActor::start("localhost:54000");
let _addr2 = addr.clone();
let res = addr.send(Command(resp_array!["GET", "test"])).await;
match res {
Ok(Err(Error::NotConnected)) => (),
_ => panic!("Should not happen {:?}", res),
}
}
#[actix_web::test]
async fn test_redis() {
env_logger::init();
let addr = RedisActor::start("127.0.0.1:6379");
let res = addr
.send(Command(resp_array!["SET", "test", "value"]))
.await;
match res {
Ok(Ok(resp)) => {
assert_eq!(resp, RespValue::SimpleString("OK".to_owned()));
let res = addr.send(Command(resp_array!["GET", "test"])).await;
match res {
Ok(Ok(resp)) => {
println!("RESP: {resp:?}");
assert_eq!(resp, RespValue::BulkString((&b"value"[..]).into()));
}
_ => panic!("Should not happen {:?}", res),
}
}
_ => panic!("Should not happen {:?}", res),
}
}

View File

@ -2,6 +2,24 @@
## Unreleased
- Add `Session::contains_key` method.
- Add `Session::update[_or]()` methods.
- Update `redis` dependency to `0.29`.
## 0.10.1
- Expose `storage::generate_session_key()` without needing to enable a crate feature.
## 0.10.0
- Add `redis-session-rustls` crate feature that enables `rustls`-secured Redis sessions.
- Add `redis-pool` crate feature (off-by-default) which enables `RedisSessionStore::{new, builder}_pooled()` constructors.
- Rename `redis-rs-session` crate feature to `redis-session`.
- Rename `redis-rs-tls-session` crate feature to `redis-session-native-tls`.
- Remove `redis-actor-session` crate feature (and, therefore, the `actix-redis` based storage backend).
- Expose `storage::generate_session_key()`.
- Update `redis` dependency to `0.26`.
## 0.9.0
- Remove use of `async-trait` on `SessionStore` trait.

View File

@ -1,13 +1,13 @@
[package]
name = "actix-session"
version = "0.9.0"
version = "0.10.1"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Luca Palmieri <rust@lpalmieri.com>",
]
description = "Session management for Actix Web"
keywords = ["http", "web", "framework", "async", "session"]
repository = "https://github.com/actix/actix-extras/tree/master/actix-session"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
@ -20,9 +20,10 @@ all-features = true
[features]
default = []
cookie-session = []
redis-actor-session = ["actix-redis", "actix", "futures-core", "rand"]
redis-rs-session = ["redis", "rand"]
redis-rs-tls-session = ["redis-rs-session", "redis/tokio-native-tls-comp"]
redis-session = ["dep:redis"]
redis-session-native-tls = ["redis-session", "redis/tokio-native-tls-comp"]
redis-session-rustls = ["redis-session", "redis/tokio-rustls-comp"]
redis-pool = ["dep:deadpool-redis"]
[dependencies]
actix-service = "2"
@ -30,31 +31,30 @@ actix-utils = "3"
actix-web = { version = "4", default-features = false, features = ["cookies", "secure-cookies"] }
anyhow = "1"
derive_more = "0.99.7"
rand = { version = "0.8", optional = true }
derive_more = { version = "2", features = ["display", "error", "from"] }
rand = "0.9"
serde = { version = "1" }
serde_json = { version = "1" }
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
# redis-actor-session
actix = { version = "0.13", default-features = false, optional = true }
actix-redis = { version = "0.12", optional = true }
futures-core = { version = "0.3.17", optional = true }
# redis-rs-session
redis = { version = "0.24", default-features = false, features = ["tokio-comp", "connection-manager"], optional = true }
# redis-session
redis = { version = "0.29", default-features = false, features = ["tokio-comp", "connection-manager"], optional = true }
deadpool-redis = { version = "0.20", optional = true }
[dev-dependencies]
actix-session = { path = ".", features = ["cookie-session", "redis-actor-session", "redis-rs-session"] }
actix-test = "0.1.0-beta.10"
actix-session = { path = ".", features = ["cookie-session", "redis-session"] }
actix-test = "0.1"
actix-web = { version = "4", default-features = false, features = ["cookies", "secure-cookies", "macros"] }
env_logger = "0.11"
log = "0.4"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing = "0.1.30"
[lints]
workspace = true
[[example]]
name = "basic"
required-features = ["redis-actor-session"]
required-features = ["redis-session"]
[[example]]
name = "authentication"
required-features = ["redis-actor-session"]
required-features = ["redis-session"]

View File

@ -5,9 +5,9 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-session?label=latest)](https://crates.io/crates/actix-session)
[![Documentation](https://docs.rs/actix-session/badge.svg?version=0.9.0)](https://docs.rs/actix-session/0.9.0)
[![Documentation](https://docs.rs/actix-session/badge.svg?version=0.10.1)](https://docs.rs/actix-session/0.10.1)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-session)
[![Dependency Status](https://deps.rs/crate/actix-session/0.9.0/status.svg)](https://deps.rs/crate/actix-session/0.9.0)
[![Dependency Status](https://deps.rs/crate/actix-session/0.10.1/status.svg)](https://deps.rs/crate/actix-session/0.10.1)
<!-- prettier-ignore-end -->
@ -25,7 +25,7 @@ We refer to the cookie used for sessions as a **session cookie**. Its content is
`actix-session` provides an easy-to-use framework to manage sessions in applications built on top of Actix Web. [`SessionMiddleware`] is the middleware underpinning the functionality provided by `actix-session`; it takes care of all the session cookie handling and instructs the **storage backend** to create/delete/update the session state based on the operations performed against the active [`Session`].
`actix-session` provides some built-in storage backends: ([`CookieSessionStore`], [`RedisSessionStore`], and [`RedisActorSessionStore`]) - you can create a custom storage backend by implementing the [`SessionStore`] trait.
`actix-session` provides some built-in storage backends: ([`CookieSessionStore`], [`RedisSessionStore`]) - you can create a custom storage backend by implementing the [`SessionStore`] trait.
Further reading on sessions:
@ -94,34 +94,26 @@ By default, `actix-session` does not provide any storage backend to retrieve and
- a purely cookie-based "backend", [`CookieSessionStore`], using the `cookie-session` feature flag.
```toml
[dependencies]
# ...
actix-session = { version = "...", features = ["cookie-session"] }
```console
cargo add actix-session --features=cookie-session
```
- a Redis-based backend via [`actix-redis`](https://docs.rs/actix-redis), [`RedisActorSessionStore`], using the `redis-actor-session` feature flag.
- a Redis-based backend via the [`redis`] crate, [`RedisSessionStore`], using the `redis-session` feature flag.
```toml
[dependencies]
# ...
actix-session = { version = "...", features = ["redis-actor-session"] }
```console
cargo add actix-session --features=redis-session
```
- a Redis-based backend via [`redis-rs`](https://docs.rs/redis-rs), [`RedisSessionStore`], using the `redis-rs-session` feature flag.
Add the `redis-session-native-tls` feature flag if you want to connect to Redis using a secure connection (via the `native-tls` crate):
```toml
[dependencies]
# ...
actix-session = { version = "...", features = ["redis-rs-session"] }
```console
cargo add actix-session --features=redis-session-native-tls
```
Add the `redis-rs-tls-session` feature flag if you want to connect to Redis using a secured connection:
If you, instead, prefer depending on `rustls`, use the `redis-session-rustls` feature flag:
```toml
[dependencies]
# ...
actix-session = { version = "...", features = ["redis-rs-session", "redis-rs-tls-session"] }
```console
cargo add actix-session --features=redis-session-rustls
```
You can implement your own session storage backend using the [`SessionStore`] trait.
@ -129,6 +121,5 @@ You can implement your own session storage backend using the [`SessionStore`] tr
[`SessionStore`]: storage::SessionStore
[`CookieSessionStore`]: storage::CookieSessionStore
[`RedisSessionStore`]: storage::RedisSessionStore
[`RedisActorSessionStore`]: storage::RedisActorSessionStore
<!-- cargo-rdme end -->

View File

@ -1,10 +1,12 @@
use actix_session::{storage::RedisActorSessionStore, Session, SessionMiddleware};
use actix_session::{storage::RedisSessionStore, Session, SessionMiddleware};
use actix_web::{
cookie::{Key, SameSite},
error::InternalError,
middleware, web, App, Error, HttpResponse, HttpServer, Responder,
};
use serde::{Deserialize, Serialize};
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
#[derive(Deserialize)]
struct Credentials {
@ -71,12 +73,21 @@ async fn secret(session: Session) -> Result<impl Responder, Error> {
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
.init();
// The signing key would usually be read from a configuration file/environment variables.
let signing_key = Key::generate();
log::info!("starting HTTP server at http://localhost:8080");
tracing::info!("setting up Redis session storage");
let storage = RedisSessionStore::new("127.0.0.1:6379").await.unwrap();
tracing::info!("starting HTTP server at http://localhost:8080");
HttpServer::new(move || {
App::new()
@ -84,10 +95,7 @@ async fn main() -> std::io::Result<()> {
.wrap(middleware::Logger::default())
// cookie session middleware
.wrap(
SessionMiddleware::builder(
RedisActorSessionStore::new("127.0.0.1:6379"),
signing_key.clone(),
)
SessionMiddleware::builder(storage.clone(), signing_key.clone())
// allow the cookie to be accessed from javascript
.cookie_http_only(false)
// allow the cookie only from the current domain

View File

@ -1,5 +1,7 @@
use actix_session::{storage::RedisActorSessionStore, Session, SessionMiddleware};
use actix_session::{storage::RedisSessionStore, Session, SessionMiddleware};
use actix_web::{cookie::Key, middleware, web, App, Error, HttpRequest, HttpServer, Responder};
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
/// simple handler
async fn index(req: HttpRequest, session: Session) -> Result<impl Responder, Error> {
@ -18,22 +20,28 @@ async fn index(req: HttpRequest, session: Session) -> Result<impl Responder, Err
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
.init();
// The signing key would usually be read from a configuration file/environment variables.
let signing_key = Key::generate();
log::info!("starting HTTP server at http://localhost:8080");
tracing::info!("setting up Redis session storage");
let storage = RedisSessionStore::new("127.0.0.1:6379").await.unwrap();
tracing::info!("starting HTTP server at http://localhost:8080");
HttpServer::new(move || {
App::new()
// enable logger
.wrap(middleware::Logger::default())
// cookie session middleware
.wrap(SessionMiddleware::new(
RedisActorSessionStore::new("127.0.0.1:6379"),
signing_key.clone(),
))
.wrap(SessionMiddleware::new(storage.clone(), signing_key.clone()))
// register simple route, handle all methods
.service(web::resource("/").to(index))
})

View File

@ -1,7 +1,7 @@
//! Configuration options to tune the behaviour of [`SessionMiddleware`].
use actix_web::cookie::{time::Duration, Key, SameSite};
use derive_more::From;
use derive_more::derive::From;
use crate::{storage::SessionStore, SessionMiddleware};

View File

@ -1,149 +1,136 @@
/*!
Session management for Actix Web.
The HTTP protocol, at a first glance, is stateless: the client sends a request, the server
parses its content, performs some processing and returns a response. The outcome is only
influenced by the provided inputs (i.e. the request content) and whatever state the server
queries while performing its processing.
Stateless systems are easier to reason about, but they are not quite as powerful as we need them
to be - e.g. how do you authenticate a user? The user would be forced to authenticate **for
every single request**. That is, for example, how 'Basic' Authentication works. While it may
work for a machine user (i.e. an API client), it is impractical for a personyou do not want a
login prompt on every single page you navigate to!
There is a solution - **sessions**. Using sessions the server can attach state to a set of
requests coming from the same client. They are built on top of cookies - the server sets a
cookie in the HTTP response (`Set-Cookie` header), the client (e.g. the browser) will store the
cookie and play it back to the server when sending new requests (using the `Cookie` header).
We refer to the cookie used for sessions as a **session cookie**. Its content is called
**session key** (or **session ID**), while the state attached to the session is referred to as
**session state**.
`actix-session` provides an easy-to-use framework to manage sessions in applications built on
top of Actix Web. [`SessionMiddleware`] is the middleware underpinning the functionality
provided by `actix-session`; it takes care of all the session cookie handling and instructs the
**storage backend** to create/delete/update the session state based on the operations performed
against the active [`Session`].
`actix-session` provides some built-in storage backends: ([`CookieSessionStore`],
[`RedisSessionStore`], and [`RedisActorSessionStore`]) - you can create a custom storage backend
by implementing the [`SessionStore`] trait.
Further reading on sessions:
- [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265);
- [OWASP's session management cheat-sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html).
# Getting started
To start using sessions in your Actix Web application you must register [`SessionMiddleware`]
as a middleware on your `App`:
```no_run
use actix_web::{web, App, HttpServer, HttpResponse, Error};
use actix_session::{Session, SessionMiddleware, storage::RedisSessionStore};
use actix_web::cookie::Key;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// When using `Key::generate()` it is important to initialize outside of the
// `HttpServer::new` closure. When deployed the secret key should be read from a
// configuration file or environment variables.
let secret_key = Key::generate();
let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379")
.await
.unwrap();
HttpServer::new(move ||
App::new()
// Add session management to your application using Redis for session state storage
.wrap(
SessionMiddleware::new(
redis_store.clone(),
secret_key.clone(),
)
)
.default_service(web::to(|| HttpResponse::Ok())))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
```
The session state can be accessed and modified by your request handlers using the [`Session`]
extractor. Note that this doesn't work in the stream of a streaming response.
```no_run
use actix_web::Error;
use actix_session::Session;
fn index(session: Session) -> Result<&'static str, Error> {
// access the session state
if let Some(count) = session.get::<i32>("counter")? {
println!("SESSION value: {}", count);
// modify the session state
session.insert("counter", count + 1)?;
} else {
session.insert("counter", 1)?;
}
Ok("Welcome!")
}
```
# Choosing A Backend
By default, `actix-session` does not provide any storage backend to retrieve and save the state
attached to your sessions. You can enable:
- a purely cookie-based "backend", [`CookieSessionStore`], using the `cookie-session` feature
flag.
```toml
[dependencies]
# ...
actix-session = { version = "...", features = ["cookie-session"] }
```
- a Redis-based backend via [`actix-redis`](https://docs.rs/actix-redis),
[`RedisActorSessionStore`], using the `redis-actor-session` feature flag.
```toml
[dependencies]
# ...
actix-session = { version = "...", features = ["redis-actor-session"] }
```
- a Redis-based backend via [`redis-rs`](https://docs.rs/redis-rs), [`RedisSessionStore`], using
the `redis-rs-session` feature flag.
```toml
[dependencies]
# ...
actix-session = { version = "...", features = ["redis-rs-session"] }
```
Add the `redis-rs-tls-session` feature flag if you want to connect to Redis using a secured
connection:
```toml
[dependencies]
# ...
actix-session = { version = "...", features = ["redis-rs-session", "redis-rs-tls-session"] }
```
You can implement your own session storage backend using the [`SessionStore`] trait.
[`SessionStore`]: storage::SessionStore
[`CookieSessionStore`]: storage::CookieSessionStore
[`RedisSessionStore`]: storage::RedisSessionStore
[`RedisActorSessionStore`]: storage::RedisActorSessionStore
*/
//! Session management for Actix Web.
//!
//! The HTTP protocol, at a first glance, is stateless: the client sends a request, the server
//! parses its content, performs some processing and returns a response. The outcome is only
//! influenced by the provided inputs (i.e. the request content) and whatever state the server
//! queries while performing its processing.
//!
//! Stateless systems are easier to reason about, but they are not quite as powerful as we need them
//! to be - e.g. how do you authenticate a user? The user would be forced to authenticate **for
//! every single request**. That is, for example, how 'Basic' Authentication works. While it may
//! work for a machine user (i.e. an API client), it is impractical for a person—you do not want a
//! login prompt on every single page you navigate to!
//!
//! There is a solution - **sessions**. Using sessions the server can attach state to a set of
//! requests coming from the same client. They are built on top of cookies - the server sets a
//! cookie in the HTTP response (`Set-Cookie` header), the client (e.g. the browser) will store the
//! cookie and play it back to the server when sending new requests (using the `Cookie` header).
//!
//! We refer to the cookie used for sessions as a **session cookie**. Its content is called
//! **session key** (or **session ID**), while the state attached to the session is referred to as
//! **session state**.
//!
//! `actix-session` provides an easy-to-use framework to manage sessions in applications built on
//! top of Actix Web. [`SessionMiddleware`] is the middleware underpinning the functionality
//! provided by `actix-session`; it takes care of all the session cookie handling and instructs the
//! **storage backend** to create/delete/update the session state based on the operations performed
//! against the active [`Session`].
//!
//! `actix-session` provides some built-in storage backends: ([`CookieSessionStore`],
//! [`RedisSessionStore`]) - you can create a custom storage backend by implementing the
//! [`SessionStore`] trait.
//!
//! Further reading on sessions:
//! - [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265);
//! - [OWASP's session management cheat-sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html).
//!
//! # Getting started
//! To start using sessions in your Actix Web application you must register [`SessionMiddleware`]
//! as a middleware on your `App`:
//!
//! ```no_run
//! use actix_web::{web, App, HttpServer, HttpResponse, Error};
//! use actix_session::{Session, SessionMiddleware, storage::RedisSessionStore};
//! use actix_web::cookie::Key;
//!
//! #[actix_web::main]
//! async fn main() -> std::io::Result<()> {
//! // When using `Key::generate()` it is important to initialize outside of the
//! // `HttpServer::new` closure. When deployed the secret key should be read from a
//! // configuration file or environment variables.
//! let secret_key = Key::generate();
//!
//! let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379")
//! .await
//! .unwrap();
//!
//! HttpServer::new(move ||
//! App::new()
//! // Add session management to your application using Redis for session state storage
//! .wrap(
//! SessionMiddleware::new(
//! redis_store.clone(),
//! secret_key.clone(),
//! )
//! )
//! .default_service(web::to(|| HttpResponse::Ok())))
//! .bind(("127.0.0.1", 8080))?
//! .run()
//! .await
//! }
//! ```
//!
//! The session state can be accessed and modified by your request handlers using the [`Session`]
//! extractor. Note that this doesn't work in the stream of a streaming response.
//!
//! ```no_run
//! use actix_web::Error;
//! use actix_session::Session;
//!
//! fn index(session: Session) -> Result<&'static str, Error> {
//! // access the session state
//! if let Some(count) = session.get::<i32>("counter")? {
//! println!("SESSION value: {}", count);
//! // modify the session state
//! session.insert("counter", count + 1)?;
//! } else {
//! session.insert("counter", 1)?;
//! }
//!
//! Ok("Welcome!")
//! }
//! ```
//!
//! # Choosing A Backend
//!
//! By default, `actix-session` does not provide any storage backend to retrieve and save the state
//! attached to your sessions. You can enable:
//!
//! - a purely cookie-based "backend", [`CookieSessionStore`], using the `cookie-session` feature
//! flag.
//!
//! ```console
//! cargo add actix-session --features=cookie-session
//! ```
//!
//! - a Redis-based backend via the [`redis`] crate, [`RedisSessionStore`], using the
//! `redis-session` feature flag.
//!
//! ```console
//! cargo add actix-session --features=redis-session
//! ```
//!
//! Add the `redis-session-native-tls` feature flag if you want to connect to Redis using a secure
//! connection (via the `native-tls` crate):
//!
//! ```console
//! cargo add actix-session --features=redis-session-native-tls
//! ```
//!
//! If you, instead, prefer depending on `rustls`, use the `redis-session-rustls` feature flag:
//!
//! ```console
//! cargo add actix-session --features=redis-session-rustls
//! ```
//!
//! You can implement your own session storage backend using the [`SessionStore`] trait.
//!
//! [`SessionStore`]: storage::SessionStore
//! [`CookieSessionStore`]: storage::CookieSessionStore
//! [`RedisSessionStore`]: storage::RedisSessionStore
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs)]
#![warn(missing_docs)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
@ -161,6 +148,7 @@ pub use self::{
};
#[cfg(test)]
#[allow(missing_docs)]
pub mod test_helpers {
use actix_web::cookie::Key;

View File

@ -47,7 +47,7 @@ use crate::{
/// # Examples
/// ```no_run
/// use actix_web::{web, App, HttpServer, HttpResponse, Error};
/// use actix_session::{Session, SessionMiddleware, storage::RedisActorSessionStore};
/// use actix_session::{Session, SessionMiddleware, storage::RedisSessionStore};
/// use actix_web::cookie::Key;
///
/// // The secret key would usually be read from a configuration file/environment variables.
@ -59,17 +59,17 @@ use crate::{
/// #[actix_web::main]
/// async fn main() -> std::io::Result<()> {
/// let secret_key = get_secret_key();
/// let redis_connection_string = "127.0.0.1:6379";
/// HttpServer::new(move ||
/// let storage = RedisSessionStore::new("127.0.0.1:6379").await.unwrap();
///
/// HttpServer::new(move || {
/// App::new()
/// // Add session management to your application using Redis for session state storage
/// .wrap(
/// SessionMiddleware::new(
/// RedisActorSessionStore::new(redis_connection_string),
/// secret_key.clone()
/// )
/// )
/// .default_service(web::to(|| HttpResponse::Ok())))
/// // Add session management to your application using Redis as storage
/// .wrap(SessionMiddleware::new(
/// storage.clone(),
/// secret_key.clone(),
/// ))
/// .default_service(web::to(|| HttpResponse::Ok()))
/// })
/// .bind(("127.0.0.1", 8080))?
/// .run()
/// .await
@ -80,7 +80,7 @@ use crate::{
///
/// ```no_run
/// use actix_web::{App, cookie::{Key, time}, Error, HttpResponse, HttpServer, web};
/// use actix_session::{Session, SessionMiddleware, storage::RedisActorSessionStore};
/// use actix_session::{Session, SessionMiddleware, storage::RedisSessionStore};
/// use actix_session::config::PersistentSession;
///
/// // The secret key would usually be read from a configuration file/environment variables.
@ -92,22 +92,20 @@ use crate::{
/// #[actix_web::main]
/// async fn main() -> std::io::Result<()> {
/// let secret_key = get_secret_key();
/// let redis_connection_string = "127.0.0.1:6379";
/// HttpServer::new(move ||
/// let storage = RedisSessionStore::new("127.0.0.1:6379").await.unwrap();
///
/// HttpServer::new(move || {
/// App::new()
/// // Customise session length!
/// .wrap(
/// SessionMiddleware::builder(
/// RedisActorSessionStore::new(redis_connection_string),
/// secret_key.clone()
/// )
/// SessionMiddleware::builder(storage.clone(), secret_key.clone())
/// .session_lifecycle(
/// PersistentSession::default()
/// .session_ttl(time::Duration::days(5))
/// PersistentSession::default().session_ttl(time::Duration::days(5)),
/// )
/// .build(),
/// )
/// .default_service(web::to(|| HttpResponse::Ok())))
/// .default_service(web::to(|| HttpResponse::Ok()))
/// })
/// .bind(("127.0.0.1", 8080))?
/// .run()
/// .await

View File

@ -14,7 +14,7 @@ use actix_web::{
FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
};
use anyhow::Context;
use derive_more::{Display, From};
use derive_more::derive::{Display, From};
use serde::{de::DeserializeOwned, Serialize};
/// The primary interface to access and modify session state.
@ -33,6 +33,9 @@ use serde::{de::DeserializeOwned, Serialize};
/// session.insert("counter", 1)?;
/// }
///
/// // or use the shorthand
/// session.update_or("counter", 1, |count: i32| count + 1);
///
/// Ok("Welcome!")
/// }
/// # actix_web::web::to(index);
@ -97,6 +100,11 @@ impl Session {
}
}
/// Returns `true` if the session contains a value for the specified `key`.
pub fn contains_key(&self, key: &str) -> bool {
self.0.borrow().state.contains_key(key)
}
/// Get all raw key-value data from the session.
///
/// Note that values are JSON encoded.
@ -114,7 +122,9 @@ impl Session {
/// Any serializable value can be used and will be encoded as JSON in session data, hence why
/// only a reference to the value is taken.
///
/// It returns an error if it fails to serialize `value` to JSON.
/// # Errors
///
/// Returns an error if JSON serialization of `value` fails.
pub fn insert<T: Serialize>(
&self,
key: impl Into<String>,
@ -132,9 +142,8 @@ impl Session {
.with_context(|| {
format!(
"Failed to serialize the provided `{}` type instance as JSON in order to \
attach as session data to the `{}` key",
attach as session data to the `{key}` key",
std::any::type_name::<T>(),
&key
)
})
.map_err(SessionInsertError)?;
@ -145,6 +154,83 @@ impl Session {
Ok(())
}
/// Updates a key-value pair into the session.
///
/// If the key exists then update it to the new value and place it back in. If the key does not
/// exist it will not be updated.
///
/// Any serializable value can be used and will be encoded as JSON in the session data, hence
/// why only a reference to the value is taken.
///
/// # Errors
///
/// Returns an error if JSON serialization of the value fails.
pub fn update<T: Serialize + DeserializeOwned, F>(
&self,
key: impl Into<String>,
updater: F,
) -> Result<(), SessionUpdateError>
where
F: FnOnce(T) -> T,
{
let mut inner = self.0.borrow_mut();
let key_str = key.into();
if let Some(val_str) = inner.state.get(&key_str) {
let value = serde_json::from_str(val_str)
.with_context(|| {
format!(
"Failed to deserialize the JSON-encoded session data attached to key \
`{key_str}` as a `{}` type",
std::any::type_name::<T>()
)
})
.map_err(SessionUpdateError)?;
let val = serde_json::to_string(&updater(value))
.with_context(|| {
format!(
"Failed to serialize the provided `{}` type instance as JSON in order to \
attach as session data to the `{key_str}` key",
std::any::type_name::<T>(),
)
})
.map_err(SessionUpdateError)?;
inner.state.insert(key_str, val);
}
Ok(())
}
/// Updates a key-value pair into the session, or inserts a default value.
///
/// If the key exists then update it to the new value and place it back in. If the key does not
/// exist the default value will be inserted instead.
///
/// Any serializable value can be used and will be encoded as JSON in session data, hence why
/// only a reference to the value is taken.
///
/// # Errors
///
/// Returns error if JSON serialization of a value fails.
pub fn update_or<T: Serialize + DeserializeOwned, F>(
&self,
key: &str,
default_value: T,
updater: F,
) -> Result<(), SessionUpdateError>
where
F: FnOnce(T) -> T,
{
if self.contains_key(key) {
self.update(key, updater)
} else {
self.insert(key, default_value)
.map_err(|err| SessionUpdateError(err.into()))
}
}
/// Remove value from the session.
///
/// If present, the JSON encoded value is returned.
@ -288,7 +374,7 @@ impl FromRequest for Session {
/// Error returned by [`Session::get`].
#[derive(Debug, Display, From)]
#[display(fmt = "{_0}")]
#[display("{_0}")]
pub struct SessionGetError(anyhow::Error);
impl StdError for SessionGetError {
@ -305,7 +391,7 @@ impl ResponseError for SessionGetError {
/// Error returned by [`Session::insert`].
#[derive(Debug, Display, From)]
#[display(fmt = "{_0}")]
#[display("{_0}")]
pub struct SessionInsertError(anyhow::Error);
impl StdError for SessionInsertError {
@ -319,3 +405,20 @@ impl ResponseError for SessionInsertError {
HttpResponse::new(self.status_code())
}
}
/// Error returned by [`Session::update`].
#[derive(Debug, Display, From)]
#[display("{_0}")]
pub struct SessionUpdateError(anyhow::Error);
impl StdError for SessionUpdateError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(self.0.as_ref())
}
}
impl ResponseError for SessionUpdateError {
fn error_response(&self) -> HttpResponse<BoxBody> {
HttpResponse::new(self.status_code())
}
}

View File

@ -31,7 +31,7 @@ impl SessionExt for ServiceResponse {
}
}
impl<'a> SessionExt for GuardContext<'a> {
impl SessionExt for GuardContext<'_> {
fn get_session(&self) -> Session {
Session::get_session(&mut self.req_data_mut())
}

View File

@ -1,7 +1,7 @@
use std::{collections::HashMap, future::Future};
use actix_web::cookie::time::Duration;
use derive_more::Display;
use derive_more::derive::Display;
use super::SessionKey;
@ -53,11 +53,11 @@ pub trait SessionStore {
#[derive(Debug, Display)]
pub enum LoadError {
/// Failed to deserialize session state.
#[display(fmt = "Failed to deserialize session state")]
#[display("Failed to deserialize session state")]
Deserialization(anyhow::Error),
/// Something went wrong when retrieving the session state.
#[display(fmt = "Something went wrong when retrieving the session state")]
#[display("Something went wrong when retrieving the session state")]
Other(anyhow::Error),
}
@ -74,11 +74,11 @@ impl std::error::Error for LoadError {
#[derive(Debug, Display)]
pub enum SaveError {
/// Failed to serialize session state.
#[display(fmt = "Failed to serialize session state")]
#[display("Failed to serialize session state")]
Serialization(anyhow::Error),
/// Something went wrong when persisting the session state.
#[display(fmt = "Something went wrong when persisting the session state")]
#[display("Something went wrong when persisting the session state")]
Other(anyhow::Error),
}
@ -95,11 +95,11 @@ impl std::error::Error for SaveError {
/// Possible failures modes for [`SessionStore::update`].
pub enum UpdateError {
/// Failed to serialize session state.
#[display(fmt = "Failed to serialize session state")]
#[display("Failed to serialize session state")]
Serialization(anyhow::Error),
/// Something went wrong when updating the session state.
#[display(fmt = "Something went wrong when updating the session state.")]
#[display("Something went wrong when updating the session state.")]
Other(anyhow::Error),
}

View File

@ -1,28 +1,19 @@
//! Pluggable storage backends for session state.
mod interface;
mod session_key;
pub use self::{
interface::{LoadError, SaveError, SessionStore, UpdateError},
session_key::SessionKey,
};
#[cfg(feature = "cookie-session")]
mod cookie;
#[cfg(feature = "redis-actor-session")]
mod redis_actor;
#[cfg(feature = "redis-rs-session")]
mod interface;
#[cfg(feature = "redis-session")]
mod redis_rs;
#[cfg(any(feature = "redis-actor-session", feature = "redis-rs-session"))]
mod session_key;
mod utils;
#[cfg(feature = "cookie-session")]
pub use cookie::CookieSessionStore;
#[cfg(feature = "redis-actor-session")]
pub use redis_actor::{RedisActorSessionStore, RedisActorSessionStoreBuilder};
#[cfg(feature = "redis-rs-session")]
pub use redis_rs::{RedisSessionStore, RedisSessionStoreBuilder};
pub use self::cookie::CookieSessionStore;
#[cfg(feature = "redis-session")]
pub use self::redis_rs::{RedisSessionStore, RedisSessionStoreBuilder};
pub use self::{
interface::{LoadError, SaveError, SessionStore, UpdateError},
session_key::SessionKey,
utils::generate_session_key,
};

View File

@ -2,7 +2,7 @@ use std::sync::Arc;
use actix_web::cookie::time::Duration;
use anyhow::Error;
use redis::{aio::ConnectionManager, AsyncCommands, Cmd, FromRedisValue, RedisResult, Value};
use redis::{aio::ConnectionManager, AsyncCommands, Client, Cmd, FromRedisValue, Value};
use super::SessionKey;
use crate::storage::{
@ -44,7 +44,7 @@ use crate::storage::{
/// ```
///
/// # TLS support
/// Add the `redis-rs-tls-session` feature flag to enable TLS support. You can then establish a TLS
/// Add the `redis-session-native-tls` or `redis-session-rustls` feature flag to enable TLS support. You can then establish a TLS
/// connection to Redis using the `rediss://` URL scheme:
///
/// ```no_run
@ -56,14 +56,38 @@ use crate::storage::{
/// # })
/// ```
///
/// # Implementation notes
/// `RedisSessionStore` leverages [`redis-rs`] as Redis client.
/// # Pooled Redis Connections
///
/// [`redis-rs`]: https://github.com/mitsuhiko/redis-rs
/// When the `redis-pool` crate feature is enabled, a pre-existing pool from [`deadpool_redis`] can
/// be provided.
///
/// ```no_run
/// use actix_session::storage::RedisSessionStore;
/// use deadpool_redis::{Config, Runtime};
///
/// let redis_cfg = Config::from_url("redis://127.0.0.1:6379");
/// let redis_pool = redis_cfg.create_pool(Some(Runtime::Tokio1)).unwrap();
///
/// let store = RedisSessionStore::new_pooled(redis_pool);
/// ```
///
/// # Implementation notes
///
/// `RedisSessionStore` leverages the [`redis`] crate as the underlying Redis client.
#[derive(Clone)]
pub struct RedisSessionStore {
configuration: CacheConfiguration,
client: ConnectionManager,
client: RedisSessionConn,
}
#[derive(Clone)]
enum RedisSessionConn {
/// Single connection.
Single(ConnectionManager),
/// Connection pool.
#[cfg(feature = "redis-pool")]
Pool(deadpool_redis::Pool),
}
#[derive(Clone)]
@ -80,34 +104,77 @@ impl Default for CacheConfiguration {
}
impl RedisSessionStore {
/// A fluent API to configure [`RedisSessionStore`].
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`] - a
/// connection string for Redis.
pub fn builder<S: Into<String>>(connection_string: S) -> RedisSessionStoreBuilder {
/// Returns a fluent API builder to configure [`RedisSessionStore`].
///
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`]
/// - a connection string for Redis.
pub fn builder(connection_string: impl Into<String>) -> RedisSessionStoreBuilder {
RedisSessionStoreBuilder {
configuration: CacheConfiguration::default(),
connection_string: connection_string.into(),
conn_builder: RedisSessionConnBuilder::Single(connection_string.into()),
}
}
/// Create a new instance of [`RedisSessionStore`] using the default configuration.
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`] - a
/// connection string for Redis.
pub async fn new<S: Into<String>>(
connection_string: S,
) -> Result<RedisSessionStore, anyhow::Error> {
/// Returns a fluent API builder to configure [`RedisSessionStore`].
///
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`]
/// - a pool object for Redis.
#[cfg(feature = "redis-pool")]
pub fn builder_pooled(pool: impl Into<deadpool_redis::Pool>) -> RedisSessionStoreBuilder {
RedisSessionStoreBuilder {
configuration: CacheConfiguration::default(),
conn_builder: RedisSessionConnBuilder::Pool(pool.into()),
}
}
/// Creates a new instance of [`RedisSessionStore`] using the default configuration.
///
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`]
/// - a connection string for Redis.
pub async fn new(connection_string: impl Into<String>) -> Result<RedisSessionStore, Error> {
Self::builder(connection_string).build().await
}
/// Creates a new instance of [`RedisSessionStore`] using the default configuration.
///
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`]
/// - a pool object for Redis.
#[cfg(feature = "redis-pool")]
pub async fn new_pooled(
pool: impl Into<deadpool_redis::Pool>,
) -> anyhow::Result<RedisSessionStore> {
Self::builder_pooled(pool).build().await
}
}
/// A fluent builder to construct a [`RedisSessionStore`] instance with custom configuration
/// parameters.
///
/// [`RedisSessionStore`]: crate::storage::RedisSessionStore
#[must_use]
pub struct RedisSessionStoreBuilder {
connection_string: String,
configuration: CacheConfiguration,
conn_builder: RedisSessionConnBuilder,
}
enum RedisSessionConnBuilder {
/// Single connection string.
Single(String),
/// Pre-built connection pool.
#[cfg(feature = "redis-pool")]
Pool(deadpool_redis::Pool),
}
impl RedisSessionConnBuilder {
async fn into_client(self) -> anyhow::Result<RedisSessionConn> {
Ok(match self {
RedisSessionConnBuilder::Single(conn_string) => {
RedisSessionConn::Single(ConnectionManager::new(Client::open(conn_string)?).await?)
}
#[cfg(feature = "redis-pool")]
RedisSessionConnBuilder::Pool(pool) => RedisSessionConn::Pool(pool),
})
}
}
impl RedisSessionStoreBuilder {
@ -120,11 +187,10 @@ impl RedisSessionStoreBuilder {
self
}
/// Finalise the builder and return a [`RedisActorSessionStore`] instance.
///
/// [`RedisActorSessionStore`]: crate::storage::RedisActorSessionStore
pub async fn build(self) -> Result<RedisSessionStore, anyhow::Error> {
let client = ConnectionManager::new(redis::Client::open(self.connection_string)?).await?;
/// Finalises builder and returns a [`RedisSessionStore`] instance.
pub async fn build(self) -> anyhow::Result<RedisSessionStore> {
let client = self.conn_builder.into_client().await?;
Ok(RedisSessionStore {
configuration: self.configuration,
client,
@ -139,7 +205,6 @@ impl SessionStore for RedisSessionStore {
let value: Option<String> = self
.execute_command(redis::cmd("GET").arg(&[&cache_key]))
.await
.map_err(Into::into)
.map_err(LoadError::Other)?;
match value {
@ -161,15 +226,19 @@ impl SessionStore for RedisSessionStore {
let session_key = generate_session_key();
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
self.execute_command(redis::cmd("SET").arg(&[
&cache_key,
&body,
"NX", // NX: only set the key if it does not already exist
"EX", // EX: set expiry
&format!("{}", ttl.whole_seconds()),
]))
self.execute_command::<()>(
redis::cmd("SET")
.arg(&[
&cache_key, // key
&body, // value
"NX", // only set the key if it does not already exist
"EX", // set expiry / TTL
])
.arg(
ttl.whole_seconds(), // EXpiry in seconds
),
)
.await
.map_err(Into::into)
.map_err(SaveError::Other)?;
Ok(session_key)
@ -187,7 +256,7 @@ impl SessionStore for RedisSessionStore {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
let v: redis::Value = self
let v: Value = self
.execute_command(redis::cmd("SET").arg(&[
&cache_key,
&body,
@ -196,7 +265,6 @@ impl SessionStore for RedisSessionStore {
&format!("{}", ttl.whole_seconds()),
]))
.await
.map_err(Into::into)
.map_err(UpdateError::Other)?;
match v {
@ -212,7 +280,7 @@ impl SessionStore for RedisSessionStore {
SaveError::Other(err) => UpdateError::Other(err),
})
}
Value::Int(_) | Value::Okay | Value::Status(_) => Ok(session_key),
Value::Int(_) | Value::Okay | Value::SimpleString(_) => Ok(session_key),
val => Err(UpdateError::Other(anyhow::anyhow!(
"Failed to update session state. {:?}",
val
@ -220,21 +288,33 @@ impl SessionStore for RedisSessionStore {
}
}
async fn update_ttl(&self, session_key: &SessionKey, ttl: &Duration) -> Result<(), Error> {
async fn update_ttl(&self, session_key: &SessionKey, ttl: &Duration) -> anyhow::Result<()> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
self.client
.clone()
.expire(&cache_key, ttl.whole_seconds())
match self.client {
RedisSessionConn::Single(ref conn) => {
conn.clone()
.expire::<_, ()>(&cache_key, ttl.whole_seconds())
.await?;
}
#[cfg(feature = "redis-pool")]
RedisSessionConn::Pool(ref pool) => {
pool.get()
.await?
.expire::<_, ()>(&cache_key, ttl.whole_seconds())
.await?;
}
}
Ok(())
}
async fn delete(&self, session_key: &SessionKey) -> Result<(), anyhow::Error> {
async fn delete(&self, session_key: &SessionKey) -> Result<(), Error> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
self.execute_command(redis::cmd("DEL").arg(&[&cache_key]))
self.execute_command::<()>(redis::cmd("DEL").arg(&[&cache_key]))
.await
.map_err(Into::into)
.map_err(UpdateError::Other)?;
Ok(())
@ -256,11 +336,15 @@ impl RedisSessionStore {
/// retry will be executed on a fresh connection, therefore it is likely to succeed (or fail for
/// a different more meaningful reason).
#[allow(clippy::needless_pass_by_ref_mut)]
async fn execute_command<T: FromRedisValue>(&self, cmd: &mut Cmd) -> RedisResult<T> {
async fn execute_command<T: FromRedisValue>(&self, cmd: &mut Cmd) -> anyhow::Result<T> {
let mut can_retry = true;
match self.client {
RedisSessionConn::Single(ref conn) => {
let mut conn = conn.clone();
loop {
match cmd.query_async(&mut self.client.clone()).await {
match cmd.query_async(&mut conn).await {
Ok(value) => return Ok(value),
Err(err) => {
if can_retry && err.is_connection_dropped() {
@ -273,7 +357,34 @@ impl RedisSessionStore {
continue;
} else {
return Err(err);
return Err(err.into());
}
}
}
}
}
#[cfg(feature = "redis-pool")]
RedisSessionConn::Pool(ref pool) => {
let mut conn = pool.get().await?;
loop {
match cmd.query_async(&mut conn).await {
Ok(value) => return Ok(value),
Err(err) => {
if can_retry && err.is_connection_dropped() {
tracing::debug!(
"Connection dropped while trying to talk to Redis. Retrying."
);
// Retry at most once
can_retry = false;
continue;
} else {
return Err(err.into());
}
}
}
}
}
@ -286,16 +397,29 @@ mod tests {
use std::collections::HashMap;
use actix_web::cookie::time;
#[cfg(not(feature = "redis-session"))]
use deadpool_redis::{Config, Runtime};
use super::*;
use crate::test_helpers::acceptance_test_suite;
async fn redis_store() -> RedisSessionStore {
#[cfg(feature = "redis-session")]
{
RedisSessionStore::new("redis://127.0.0.1:6379")
.await
.unwrap()
}
#[cfg(not(feature = "redis-session"))]
{
let redis_pool = Config::from_url("redis://127.0.0.1:6379")
.create_pool(Some(Runtime::Tokio1))
.unwrap();
RedisSessionStore::new(redis_pool.clone())
}
}
#[actix_web::test]
async fn test_session_workflow() {
let redis_store = redis_store().await;
@ -313,12 +437,25 @@ mod tests {
async fn loading_an_invalid_session_state_returns_deserialization_error() {
let store = redis_store().await;
let session_key = generate_session_key();
store
.client
match store.client {
RedisSessionConn::Single(ref conn) => conn
.clone()
.set::<_, _, ()>(session_key.as_ref(), "random-thing-which-is-not-json")
.await
.unwrap(),
#[cfg(feature = "redis-pool")]
RedisSessionConn::Pool(ref pool) => {
pool.get()
.await
.unwrap()
.set::<_, _, ()>(session_key.as_ref(), "random-thing-which-is-not-json")
.await
.unwrap();
}
}
assert!(matches!(
store.load(&session_key).await.unwrap_err(),
LoadError::Deserialization(_),

View File

@ -1,4 +1,4 @@
use derive_more::{Display, From};
use derive_more::derive::{Display, From};
/// A session key, the string stored in a client-side cookie to associate a user with its session
/// state on the backend.
@ -7,7 +7,7 @@ use derive_more::{Display, From};
/// Session keys are stored as cookies, therefore they cannot be arbitrary long. Session keys are
/// required to be smaller than 4064 bytes.
///
/// ```rust
/// ```
/// use actix_session::storage::SessionKey;
///
/// let key: String = std::iter::repeat('a').take(4065).collect();
@ -45,7 +45,7 @@ impl From<SessionKey> for String {
}
#[derive(Debug, Display, From)]
#[display(fmt = "The provided string is not a valid session key")]
#[display("The provided string is not a valid session key")]
pub struct InvalidSessionKeyError(anyhow::Error);
impl std::error::Error for InvalidSessionKeyError {

View File

@ -1,17 +1,13 @@
use rand::{distributions::Alphanumeric, rngs::OsRng, Rng as _};
use rand::distr::{Alphanumeric, SampleString as _};
use crate::storage::SessionKey;
/// Session key generation routine that follows [OWASP recommendations].
///
/// [OWASP recommendations]: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy
pub(crate) fn generate_session_key() -> SessionKey {
let value = std::iter::repeat(())
.map(|()| OsRng.sample(Alphanumeric))
.take(64)
.collect::<Vec<_>>();
// These unwraps will never panic because pre-conditions are always verified
// (i.e. length and character set)
String::from_utf8(value).unwrap().try_into().unwrap()
pub fn generate_session_key() -> SessionKey {
Alphanumeric
.sample_string(&mut rand::rng(), 64)
.try_into()
.expect("generated string should be within size range for a session key")
}

View File

@ -69,6 +69,16 @@ async fn session_entries() {
map.contains_key("test_num");
}
#[actix_web::test]
async fn session_contains_key() {
let req = test::TestRequest::default().to_srv_request();
let session = req.get_session();
session.insert("test_str", "val").unwrap();
session.insert("test_str", 1).unwrap();
assert!(session.contains_key("test_str"));
assert!(!session.contains_key("test_num"));
}
#[actix_web::test]
async fn insert_session_after_renew() {
let session = test::TestRequest::default().to_srv_request().get_session();
@ -83,6 +93,35 @@ async fn insert_session_after_renew() {
assert_eq!(session.status(), SessionStatus::Renewed);
}
#[actix_web::test]
async fn update_session() {
let session = test::TestRequest::default().to_srv_request().get_session();
session.update("test_val", |c: u32| c + 1).unwrap();
assert_eq!(session.status(), SessionStatus::Unchanged);
session.insert("test_val", 0).unwrap();
assert_eq!(session.status(), SessionStatus::Changed);
session.update("test_val", |c: u32| c + 1).unwrap();
assert_eq!(session.get("test_val").unwrap(), Some(1));
session.update("test_val", |c: u32| c + 1).unwrap();
assert_eq!(session.get("test_val").unwrap(), Some(2));
}
#[actix_web::test]
async fn update_or_session() {
let session = test::TestRequest::default().to_srv_request().get_session();
session.update_or("test_val", 1, |c: u32| c + 1).unwrap();
assert_eq!(session.status(), SessionStatus::Changed);
assert_eq!(session.get("test_val").unwrap(), Some(1));
session.update_or("test_val", 1, |c: u32| c + 1).unwrap();
assert_eq!(session.get("test_val").unwrap(), Some(2));
}
#[actix_web::test]
async fn remove_session_after_renew() {
let session = test::TestRequest::default().to_srv_request().get_session();

View File

@ -2,6 +2,13 @@
## Unreleased
## 0.8.0
- Add `openssl` crate feature for TLS settings using OpenSSL.
- Add `ApplySettings::try_apply_settings()`.
- Implement TLS logic for `ApplySettings::try_apply_settings()`.
- Add `Tls::get_ssl_acceptor_builder()` function to build `openssl::ssl::SslAcceptorBuilder`.
- Deprecate `ApplySettings::apply_settings()`.
- Minimum supported Rust version (MSRV) is now 1.75.
## 0.7.1

View File

@ -1,25 +1,30 @@
[package]
name = "actix-settings"
version = "0.7.1"
version = "0.8.0"
authors = [
"Joey Ezechiels <joey.ezechiels@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
]
description = "Easily manage Actix Web's settings from a TOML file and environment variables"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
[features]
openssl = ["dep:openssl", "actix-web/openssl"]
[dependencies]
actix-http = "3"
actix-service = "2"
actix-web = { version = "4", default-features = false }
derive_more = "0.99.7"
once_cell = "1.13"
derive_more = { version = "2", features = ["display", "error"] }
once_cell = "1.21"
openssl = { version = "0.10", features = ["v110"], optional = true }
regex = "1.5"
serde = { version = "1", features = ["derive"] }
toml = "0.8"
@ -27,3 +32,6 @@ toml = "0.8"
[dev-dependencies]
actix-web = "4"
env_logger = "0.11"
[lints]
workspace = true

View File

@ -5,9 +5,9 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-settings?label=latest)](https://crates.io/crates/actix-settings)
[![Documentation](https://docs.rs/actix-settings/badge.svg?version=0.7.1)](https://docs.rs/actix-settings/0.7.1)
[![Documentation](https://docs.rs/actix-settings/badge.svg?version=0.8.0)](https://docs.rs/actix-settings/0.8.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-settings)
[![Dependency Status](https://deps.rs/crate/actix-settings/0.7.1/status.svg)](https://deps.rs/crate/actix-settings/0.7.1)
[![Dependency Status](https://deps.rs/crate/actix-settings/0.8.0/status.svg)](https://deps.rs/crate/actix-settings/0.8.0)
<!-- prettier-ignore-end -->
@ -23,10 +23,6 @@ There is a way to extend the available settings. This can be used to combine the
Have a look at [the usage example][usage] to see how.
## WIP
Configuration options for TLS set up are not yet implemented.
## Special Thanks
This crate was made possible by support from Accept B.V and [@jjpe].

View File

@ -57,7 +57,7 @@ async fn main() -> std::io::Result<()> {
}
})
// apply the `Settings` to Actix Web's `HttpServer`
.apply_settings(&settings)
.try_apply_settings(&settings)?
.run()
.await
}

View File

@ -1,22 +1,24 @@
use std::{env::VarError, io, num::ParseIntError, path::PathBuf, str::ParseBoolError};
use derive_more::{Display, Error};
use derive_more::derive::{Display, Error};
#[cfg(feature = "openssl")]
use openssl::error::ErrorStack as OpenSSLError;
use toml::de::Error as TomlError;
/// Errors that can be returned from methods in this crate.
#[derive(Debug, Display, Error)]
pub enum Error {
/// Environment variable does not exists or is invalid.
#[display(fmt = "Env var error: {_0}")]
#[display("Env var error: {_0}")]
EnvVarError(VarError),
/// File already exists on disk.
#[display(fmt = "File exists: {}", "_0.display()")]
#[display("File exists: {}", _0.display())]
FileExists(#[error(not(source))] PathBuf),
/// Invalid value.
#[allow(missing_docs)]
#[display(fmt = "Expected {expected}, got {got} (@ {file}:{line}:{column})")]
#[display("Expected {expected}, got {got} (@ {file}:{line}:{column})")]
InvalidValue {
expected: &'static str,
got: String,
@ -26,23 +28,28 @@ pub enum Error {
},
/// I/O error.
#[display(fmt = "")]
#[display("I/O error: {_0}")]
IoError(io::Error),
/// OpenSSL Error.
#[cfg(feature = "openssl")]
#[display("OpenSSL error: {_0}")]
OpenSSLError(OpenSSLError),
/// Value is not a boolean.
#[display(fmt = "Failed to parse boolean: {_0}")]
#[display("Failed to parse boolean: {_0}")]
ParseBoolError(ParseBoolError),
/// Value is not an integer.
#[display(fmt = "Failed to parse integer: {_0}")]
#[display("Failed to parse integer: {_0}")]
ParseIntError(ParseIntError),
/// Value is not an address.
#[display(fmt = "Failed to parse address: {_0}")]
#[display("Failed to parse address: {_0}")]
ParseAddressError(#[error(not(source))] String),
/// Error deserializing as TOML.
#[display(fmt = "TOML error: {_0}")]
#[display("TOML error: {_0}")]
TomlError(TomlError),
}
@ -64,6 +71,13 @@ impl From<io::Error> for Error {
}
}
#[cfg(feature = "openssl")]
impl From<OpenSSLError> for Error {
fn from(err: OpenSSLError) -> Self {
Self::OpenSSLError(err)
}
}
impl From<ParseBoolError> for Error {
fn from(err: ParseBoolError) -> Self {
Self::ParseBoolError(err)
@ -101,6 +115,9 @@ impl From<Error> for io::Error {
Error::IoError(io_error) => io_error,
#[cfg(feature = "openssl")]
Error::OpenSSLError(ossl_error) => io::Error::new(io::ErrorKind::Other, ossl_error),
Error::ParseBoolError(_) => {
io::Error::new(io::ErrorKind::InvalidInput, err.to_string())
}

View File

@ -54,15 +54,14 @@
//! }
//! })
//! // apply the `Settings` to Actix Web's `HttpServer`
//! .apply_settings(&settings)
//! .try_apply_settings(&settings)?
//! .run()
//! .await
//! }
//! ```
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
#![warn(missing_docs, missing_debug_implementations)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
@ -90,12 +89,14 @@ mod error;
mod parse;
mod settings;
#[cfg(feature = "openssl")]
pub use self::settings::Tls;
pub use self::{
error::Error,
parse::Parse,
settings::{
ActixSettings, Address, Backlog, KeepAlive, MaxConnectionRate, MaxConnections, Mode,
NumWorkers, Timeout, Tls,
NumWorkers, Timeout,
},
};
@ -240,10 +241,28 @@ where
}
/// Extension trait for applying parsed settings to the server object.
pub trait ApplySettings<S> {
/// Apply some settings object value to `self`.
#[must_use]
fn apply_settings(self, settings: &S) -> Self;
pub trait ApplySettings<S>: Sized {
/// Applies some settings object value to `self`.
///
/// The default implementation calls [`try_apply_settings()`].
///
/// # Panics
///
/// May panic if settings are invalid or cannot be applied.
///
/// [`try_apply_settings()`]: ApplySettings::try_apply_settings().
#[deprecated = "Prefer `try_apply_settings()`."]
fn apply_settings(self, settings: &S) -> Self {
self.try_apply_settings(settings)
.expect("Could not apply settings")
}
/// Applies some settings object value to `self`.
///
/// # Errors
///
/// May return error if settings are invalid or cannot be applied.
fn try_apply_settings(self, settings: &S) -> AsResult<Self>;
}
impl<F, I, S, B> ApplySettings<ActixSettings> for HttpServer<F, I, S, B>
@ -257,17 +276,27 @@ where
S::Future: 'static,
B: MessageBody + 'static,
{
fn apply_settings(mut self, settings: &ActixSettings) -> Self {
if settings.tls.enabled {
// for Address { host, port } in &settings.actix.hosts {
// self = self.bind(format!("{}:{}", host, port))
// .unwrap(/*TODO*/);
// }
unimplemented!("[ApplySettings] TLS support has not been implemented yet.");
} else {
fn apply_settings(self, settings: &ActixSettings) -> Self {
self.try_apply_settings(settings).unwrap()
}
fn try_apply_settings(mut self, settings: &ActixSettings) -> AsResult<Self> {
for Address { host, port } in &settings.hosts {
self = self.bind(format!("{host}:{port}"))
.unwrap(/*TODO*/);
#[cfg(feature = "openssl")]
{
if settings.tls.enabled {
self = self.bind_openssl(
format!("{}:{}", host, port),
settings.tls.get_ssl_acceptor_builder()?,
)?;
} else {
self = self.bind(format!("{host}:{port}"))?;
}
}
#[cfg(not(feature = "openssl"))]
{
self = self.bind(format!("{host}:{port}"))?;
}
}
@ -320,7 +349,7 @@ where
Timeout::Seconds(n) => self.shutdown_timeout(n as u64),
};
self
Ok(self)
}
}
@ -337,7 +366,11 @@ where
A: de::DeserializeOwned,
{
fn apply_settings(self, settings: &BasicSettings<A>) -> Self {
self.apply_settings(&settings.actix)
self.try_apply_settings(&settings.actix).unwrap()
}
fn try_apply_settings(self, settings: &BasicSettings<A>) -> AsResult<Self> {
self.try_apply_settings(&settings.actix)
}
}
@ -350,7 +383,8 @@ mod tests {
#[test]
fn apply_settings() {
let settings = Settings::parse_toml("Server.toml").unwrap();
let _ = HttpServer::new(App::new).apply_settings(&settings);
let server = HttpServer::new(App::new).try_apply_settings(&settings);
assert!(server.is_ok());
}
#[test]
@ -663,6 +697,7 @@ mod tests {
assert_eq!(settings.actix.shutdown_timeout, Timeout::Seconds(42));
}
#[cfg(feature = "openssl")]
#[test]
fn override_field_tls_enabled() {
let mut settings = Settings::from_default_template();
@ -671,6 +706,7 @@ mod tests {
assert!(settings.actix.tls.enabled);
}
#[cfg(feature = "openssl")]
#[test]
fn override_field_with_env_var_tls_enabled() {
let mut settings = Settings::from_default_template();
@ -684,6 +720,7 @@ mod tests {
assert!(settings.actix.tls.enabled);
}
#[cfg(feature = "openssl")]
#[test]
fn override_field_tls_certificate() {
let mut settings = Settings::from_default_template();
@ -702,6 +739,7 @@ mod tests {
);
}
#[cfg(feature = "openssl")]
#[test]
fn override_field_with_env_var_tls_certificate() {
let mut settings = Settings::from_default_template();
@ -724,6 +762,7 @@ mod tests {
);
}
#[cfg(feature = "openssl")]
#[test]
fn override_field_tls_private_key() {
let mut settings = Settings::from_default_template();
@ -742,6 +781,7 @@ mod tests {
);
}
#[cfg(feature = "openssl")]
#[test]
fn override_field_with_env_var_tls_private_key() {
let mut settings = Settings::from_default_template();

View File

@ -43,7 +43,7 @@ impl<'de> de::Deserialize<'de> for Backlog {
{
struct BacklogVisitor;
impl<'de> de::Visitor<'de> for BacklogVisitor {
impl de::Visitor<'_> for BacklogVisitor {
type Value = Backlog;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@ -68,7 +68,7 @@ impl<'de> de::Deserialize<'de> for KeepAlive {
{
struct KeepAliveVisitor;
impl<'de> de::Visitor<'de> for KeepAliveVisitor {
impl de::Visitor<'_> for KeepAliveVisitor {
type Value = KeepAlive;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@ -40,7 +40,7 @@ impl<'de> de::Deserialize<'de> for MaxConnectionRate {
{
struct MaxConnectionRateVisitor;
impl<'de> de::Visitor<'de> for MaxConnectionRateVisitor {
impl de::Visitor<'_> for MaxConnectionRateVisitor {
type Value = MaxConnectionRate;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@ -40,7 +40,7 @@ impl<'de> de::Deserialize<'de> for MaxConnections {
{
struct MaxConnectionsVisitor;
impl<'de> de::Visitor<'de> for MaxConnectionsVisitor {
impl de::Visitor<'_> for MaxConnectionsVisitor {
type Value = MaxConnections;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@ -8,12 +8,15 @@ mod max_connections;
mod mode;
mod num_workers;
mod timeout;
#[cfg(feature = "openssl")]
mod tls;
#[cfg(feature = "openssl")]
pub use self::tls::Tls;
pub use self::{
address::Address, backlog::Backlog, keep_alive::KeepAlive,
max_connection_rate::MaxConnectionRate, max_connections::MaxConnections, mode::Mode,
num_workers::NumWorkers, timeout::Timeout, tls::Tls,
num_workers::NumWorkers, timeout::Timeout,
};
/// Settings types for Actix Web.
@ -57,5 +60,6 @@ pub struct ActixSettings {
pub shutdown_timeout: Timeout,
/// TLS (HTTPS) configuration.
#[cfg(feature = "openssl")]
pub tls: Tls,
}

View File

@ -39,7 +39,7 @@ impl<'de> de::Deserialize<'de> for NumWorkers {
{
struct NumWorkersVisitor;
impl<'de> de::Visitor<'de> for NumWorkersVisitor {
impl de::Visitor<'_> for NumWorkersVisitor {
type Value = NumWorkers;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@ -71,7 +71,7 @@ impl<'de> de::Deserialize<'de> for Timeout {
{
struct TimeoutVisitor;
impl<'de> de::Visitor<'de> for TimeoutVisitor {
impl de::Visitor<'_> for TimeoutVisitor {
type Value = Timeout;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@ -1,13 +1,16 @@
use std::path::PathBuf;
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
use serde::Deserialize;
use crate::AsResult;
/// TLS (HTTPS) configuration.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[doc(alias = "ssl", alias = "https")]
pub struct Tls {
/// Tru if accepting TLS connections should be enabled.
/// True if accepting TLS connections should be enabled.
pub enabled: bool,
/// Path to certificate `.pem` file.
@ -16,3 +19,39 @@ pub struct Tls {
/// Path to private key `.pem` file.
pub private_key: PathBuf,
}
impl Tls {
/// Returns an [`SslAcceptorBuilder`] with the configured settings.
///
/// The result is often used with [`actix_web::HttpServer::bind_openssl()`].
///
/// # Example
///
/// ```no_run
/// use std::io;
/// use actix_settings::{ApplySettings as _, Settings};
/// use actix_web::{get, web, App, HttpServer, Responder};
///
/// #[actix_web::main]
/// async fn main() -> io::Result<()> {
/// let settings = Settings::from_default_template();
///
/// HttpServer::new(|| {
/// App::new().route("/", web::to(|| async { "Hello, World!" }))
/// })
/// .try_apply_settings(&settings)?
/// .bind(("127.0.0.1", 8080))?
/// .bind_openssl(("127.0.0.1", 8443), settings.actix.tls.get_ssl_acceptor_builder()?)?
/// .run()
/// .await
/// }
/// ```
pub fn get_ssl_acceptor_builder(&self) -> AsResult<SslAcceptorBuilder> {
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?;
builder.set_certificate_chain_file(&self.certificate)?;
builder.set_private_key_file(&self.private_key, SslFiletype::PEM)?;
builder.check_private_key()?;
Ok(builder)
}
}

View File

@ -2,6 +2,8 @@
## Unreleased
## 0.8.2
- Minimum supported Rust version (MSRV) is now 1.75.
## 0.8.1

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web-httpauth"
version = "0.8.1"
version = "0.8.2"
description = "HTTP authentication schemes for Actix Web"
categories = ["web-programming"]
keywords = ["http", "web", "framework", "authentication", "security"]
@ -8,8 +8,8 @@ authors = [
"svartalf <self@svartalf.info>",
"Yuki Okushi <huyuumi.dev@gmail.com>",
]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
@ -32,3 +32,8 @@ pin-project-lite = "0.2.7"
actix-cors = "0.7"
actix-service = "2"
actix-web = { version = "4.1", default-features = false, features = ["macros"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing = "0.1.30"
[lints]
workspace = true

View File

@ -5,9 +5,9 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-web-httpauth?label=latest)](https://crates.io/crates/actix-web-httpauth)
[![Documentation](https://docs.rs/actix-web-httpauth/badge.svg?version=0.8.1)](https://docs.rs/actix-web-httpauth/0.8.1)
[![Documentation](https://docs.rs/actix-web-httpauth/badge.svg?version=0.8.2)](https://docs.rs/actix-web-httpauth/0.8.2)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-web-httpauth)
[![Dependency Status](https://deps.rs/crate/actix-web-httpauth/0.8.1/status.svg)](https://deps.rs/crate/actix-web-httpauth/0.8.1)
[![Dependency Status](https://deps.rs/crate/actix-web-httpauth/0.8.2/status.svg)](https://deps.rs/crate/actix-web-httpauth/0.8.2)
<!-- prettier-ignore-end -->

View File

@ -1,24 +1,57 @@
use actix_web::{dev::ServiceRequest, middleware, web, App, Error, HttpServer};
use actix_web_httpauth::{extractors::basic::BasicAuth, middleware::HttpAuthentication};
use actix_web::{
dev::ServiceRequest, error, get, middleware::Logger, App, Error, HttpServer, Responder,
};
use actix_web_httpauth::{extractors::bearer::BearerAuth, middleware::HttpAuthentication};
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
/// Validator that:
/// - accepts Bearer auth;
/// - returns a custom response for requests without a valid Bearer Authorization header;
/// - rejects tokens containing an "x" (for quick testing using command line HTTP clients).
async fn validator(
req: ServiceRequest,
_credentials: BasicAuth,
credentials: Option<BearerAuth>,
) -> Result<ServiceRequest, (Error, ServiceRequest)> {
let Some(credentials) = credentials else {
return Err((error::ErrorBadRequest("no bearer header"), req));
};
eprintln!("{credentials:?}");
if credentials.token().contains('x') {
return Err((error::ErrorBadRequest("token contains x"), req));
}
Ok(req)
}
#[get("/")]
async fn index(auth: BearerAuth) -> impl Responder {
format!("authenticated for token: {}", auth.token().to_owned())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
.without_time()
.init();
HttpServer::new(|| {
let auth = HttpAuthentication::basic(validator);
let auth = HttpAuthentication::with_fn(validator);
App::new()
.wrap(middleware::Logger::default())
.service(index)
.wrap(auth)
.service(web::resource("/").to(|| async { "Test\r\n" }))
.wrap(Logger::default().log_target("@"))
})
.bind("127.0.0.1:8080")?
.workers(1)
.workers(2)
.run()
.await
}

View File

@ -15,8 +15,7 @@
//! [Middleware]: self::middleware
#![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs)]
#![warn(missing_docs)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

View File

@ -43,6 +43,55 @@ where
{
/// Construct `HttpAuthentication` middleware with the provided auth extractor `T` and
/// validation callback `F`.
///
/// This function can be used to implement optional authentication and/or custom responses to
/// missing authentication.
///
/// # Examples
///
/// ## Required Basic Auth
///
/// ```no_run
/// # use actix_web_httpauth::extractors::basic::BasicAuth;
/// # use actix_web::dev::ServiceRequest;
/// async fn validator(
/// req: ServiceRequest,
/// credentials: BasicAuth,
/// ) -> Result<ServiceRequest, (actix_web::Error, ServiceRequest)> {
/// eprintln!("{credentials:?}");
///
/// if credentials.user_id().contains('x') {
/// return Err((actix_web::error::ErrorBadRequest("user ID contains x"), req));
/// }
///
/// Ok(req)
/// }
/// # actix_web_httpauth::middleware::HttpAuthentication::with_fn(validator);
/// ```
///
/// ## Optional Bearer Auth
///
/// ```no_run
/// # use actix_web_httpauth::extractors::bearer::BearerAuth;
/// # use actix_web::dev::ServiceRequest;
/// async fn validator(
/// req: ServiceRequest,
/// credentials: Option<BearerAuth>,
/// ) -> Result<ServiceRequest, (actix_web::Error, ServiceRequest)> {
/// let Some(credentials) = credentials else {
/// return Err((actix_web::error::ErrorBadRequest("no bearer header"), req));
/// };
///
/// eprintln!("{credentials:?}");
///
/// if credentials.token().contains('x') {
/// return Err((actix_web::error::ErrorBadRequest("token contains x"), req));
/// }
///
/// Ok(req)
/// }
/// # actix_web_httpauth::middleware::HttpAuthentication::with_fn(validator);
/// ```
pub fn with_fn(process_fn: F) -> HttpAuthentication<T, F> {
HttpAuthentication {
process_fn: Arc::new(process_fn),

View File

@ -12,8 +12,8 @@ struct Quoted<'a> {
state: State,
}
impl<'a> Quoted<'a> {
pub fn new(s: &'a str) -> Quoted<'_> {
impl Quoted<'_> {
pub fn new(s: &str) -> Quoted<'_> {
Quoted {
inner: s.split('"').peekable(),
state: State::YieldStr,

View File

@ -2,8 +2,16 @@
## Unreleased
- Ensure TCP connection is properly shut down when session is dropped.
## 0.3.0
- Add `AggregatedMessage[Stream]` types.
- Add `MessageStream::max_frame_size()` setter method.
- Add `Session::continuation()` method.
- The `Session::text()` method now receives an `impl Into<ByteString>`, making broadcasting text messages more efficient.
- Remove type parameters from `Session::{text, binary}()` methods, replacing with equivalent `impl Trait` parameters.
- `Session::text()` now receives an `impl Into<ByteString>`, making broadcasting text messages more efficient.
- Reduce memory usage by `take`-ing (rather than `split`-ing) the encoded buffer when yielding bytes in the response stream.
## 0.2.5

View File

@ -1,14 +1,15 @@
[package]
name = "actix-ws"
version = "0.2.0"
version = "0.3.0"
description = "WebSockets for Actix Web, without actors"
categories = ["web-programming::websocket"]
keywords = ["actix", "web", "websocket", "websockets", "http"]
keywords = ["actix", "web", "websocket", "websockets", "streaming"]
authors = [
"asonix <asonix@asonix.dog>",
"Rob Ede <robjtede@icloud.com>",
]
repository = "https://github.com/actix/actix-extras"
repository.workspace = true
homepage.workspace = true
license.workspace = true
edition.workspace = true
rust-version.workspace = true
@ -19,13 +20,14 @@ actix-http = { version = "3", default-features = false, features = ["ws"] }
actix-web = { version = "4", default-features = false }
bytestring = "1"
futures-core = "0.3.17"
tokio = { version = "1", features = ["sync"] }
tokio = { version = "1.24", features = ["sync"] }
[dev-dependencies]
actix-rt = "2.6"
actix-web = "4.0.1"
anyhow = "1.0"
futures-util = "0.3.17"
log = "0.4"
pretty_env_logger = "0.5"
tokio = { version = "1", features = ["sync"] }
actix-web = "4.8"
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
tokio = { version = "1.24", features = ["sync", "rt", "macros"] }
tracing = "0.1.30"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
[lints]
workspace = true

View File

@ -1,48 +1,38 @@
# Actix WS (Next Gen)
# `actix-ws`
> WebSockets for Actix Web, without actors.
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-ws?label=latest)](https://crates.io/crates/actix-ws)
[![Documentation](https://docs.rs/actix-ws/badge.svg?version=0.2.0)](https://docs.rs/actix-ws/0.2.0)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-ws)
[![Dependency Status](https://deps.rs/crate/actix-ws/0.2.0/status.svg)](https://deps.rs/crate/actix-ws/0.2.0)
[![Documentation](https://docs.rs/actix-ws/badge.svg?version=0.3.0)](https://docs.rs/actix-ws/0.3.0)
![Version](https://img.shields.io/badge/rustc-1.75+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-ws.svg)
<br />
[![Dependency Status](https://deps.rs/crate/actix-ws/0.3.0/status.svg)](https://deps.rs/crate/actix-ws/0.3.0)
[![Download](https://img.shields.io/crates/d/actix-ws.svg)](https://crates.io/crates/actix-ws)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-ws)
- [Example Chat Project](https://github.com/actix/examples/tree/master/websockets/chat-actorless)
- Minimum Supported Rust Version (MSRV): 1.75
## Usage
```toml
# Cargo.toml
anyhow = "1"
actix-web = "4"
actix-ws-ng = "0.3"
```
## Example
```rust
// main.rs
use actix_web::{middleware::Logger, web, App, Error, HttpRequest, HttpResponse, HttpServer};
use actix_web::{middleware::Logger, web, App, HttpRequest, HttpServer, Responder};
use actix_ws::Message;
async fn ws(req: HttpRequest, body: web::Payload) -> Result<HttpResponse, Error> {
async fn ws(req: HttpRequest, body: web::Payload) -> actix_web::Result<impl Responder> {
let (response, mut session, mut msg_stream) = actix_ws::handle(&req, body)?;
actix_rt::spawn(async move {
while let Some(Ok(msg)) = msg_stream.next().await {
actix_web::rt::spawn(async move {
while let Some(Ok(msg)) = msg_stream.recv().await {
match msg {
Message::Ping(bytes) => {
if session.pong(&bytes).await.is_err() {
return;
}
}
Message::Text(s) => println!("Got text, {}", s),
Message::Text(msg) => println!("Got text: {msg}"),
_ => break,
}
}
@ -54,7 +44,7 @@ async fn ws(req: HttpRequest, body: web::Payload) -> Result<HttpResponse, Error>
}
#[actix_web::main]
async fn main() -> Result<(), anyhow::Error> {
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.wrap(Logger::default())
@ -68,6 +58,12 @@ async fn main() -> Result<(), anyhow::Error> {
}
```
## Resources
- [API Documentation](https://docs.rs/actix-ws)
- [Example Chat Project](https://github.com/actix/examples/tree/master/websockets/chat-actorless)
- Minimum Supported Rust Version (MSRV): 1.75
## License
This project is licensed under either of

View File

@ -0,0 +1,69 @@
<html>
<head>
<meta charset="utf-8" />
<title>Chat</title>
<script>
function onLoad() {
console.log("BOOTING");
const socket = new WebSocket("ws://localhost:8080/ws");
const input = document.getElementById("chat-input");
const logs = document.getElementById("chat-logs");
if (!input || !logs) {
alert("Couldn't find required elements");
console.err("Couldn't find required elements");
return;
}
input.addEventListener(
"keyup",
(event) => {
if (event.isComposing) {
return;
}
if (event.key != "Enter") {
return;
}
socket.send(input.value);
input.value = "";
},
false
);
socket.onmessage = (event) => {
const newNode = document.createElement("li");
newNode.textContent = event.data;
let firstChild = null;
for (const n of logs.childNodes.values()) {
if (n.nodeType == 1) {
firstChild = n;
break;
}
}
if (firstChild) {
logs.insertBefore(newNode, firstChild);
} else {
logs.appendChild(newNode);
}
};
window.addEventListener("beforeunload", () => {
socket.close();
});
}
if (document.readyState === "complete") {
onLoad();
} else {
document.addEventListener("DOMContentLoaded", onLoad, false);
}
</script>
</head>
<body>
<input id="chat-input" type="test" />
<ul id="chat-logs"></ul>
</body>
</html>

View File

@ -1,13 +1,18 @@
use std::{
io,
sync::Arc,
time::{Duration, Instant},
};
use actix_web::{middleware::Logger, web, App, HttpRequest, HttpResponse, HttpServer};
use actix_ws::{Message, Session};
use actix_web::{
middleware::Logger, web, web::Html, App, HttpRequest, HttpResponse, HttpServer, Responder,
};
use actix_ws::{AggregatedMessage, Session};
use bytestring::ByteString;
use futures_util::{stream::FuturesUnordered, StreamExt as _};
use log::info;
use tokio::sync::Mutex;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
#[derive(Clone)]
struct Chat {
@ -31,15 +36,19 @@ impl Chat {
self.inner.lock().await.sessions.push(session);
}
async fn send(&self, msg: String) {
async fn send(&self, msg: impl Into<ByteString>) {
let msg = msg.into();
let mut inner = self.inner.lock().await;
let mut unordered = FuturesUnordered::new();
for mut session in inner.sessions.drain(..) {
let msg = msg.clone();
unordered.push(async move {
let res = session.text(msg).await;
res.map(|_| session).map_err(|_| info!("Dropping session"))
res.map(|_| session)
.map_err(|_| tracing::debug!("Dropping session"))
});
}
@ -56,17 +65,21 @@ async fn ws(
body: web::Payload,
chat: web::Data<Chat>,
) -> Result<HttpResponse, actix_web::Error> {
let (response, mut session, mut stream) = actix_ws::handle(&req, body)?;
let (response, mut session, stream) = actix_ws::handle(&req, body)?;
// increase the maximum allowed frame size to 128KiB and aggregate continuation frames
let mut stream = stream.max_frame_size(128 * 1024).aggregate_continuations();
chat.insert(session.clone()).await;
info!("Inserted session");
tracing::info!("Inserted session");
let alive = Arc::new(Mutex::new(Instant::now()));
let mut session2 = session.clone();
let alive2 = alive.clone();
actix_rt::spawn(async move {
let mut interval = actix_rt::time::interval(Duration::from_secs(5));
actix_web::rt::spawn(async move {
let mut interval = actix_web::rt::time::interval(Duration::from_secs(5));
loop {
interval.tick().await;
if session2.ping(b"").await.is_err() {
@ -80,117 +93,54 @@ async fn ws(
}
});
actix_rt::spawn(async move {
while let Some(Ok(msg)) = stream.next().await {
actix_web::rt::spawn(async move {
while let Some(Ok(msg)) = stream.recv().await {
match msg {
Message::Ping(bytes) => {
AggregatedMessage::Ping(bytes) => {
if session.pong(&bytes).await.is_err() {
return;
}
}
Message::Text(s) => {
info!("Relaying text, {}", s);
let s: &str = s.as_ref();
chat.send(s.into()).await;
AggregatedMessage::Text(string) => {
tracing::info!("Relaying text, {string}");
chat.send(string).await;
}
Message::Close(reason) => {
AggregatedMessage::Close(reason) => {
let _ = session.close(reason).await;
info!("Got close, bailing");
tracing::info!("Got close, bailing");
return;
}
Message::Continuation(_) => {
let _ = session.close(None).await;
info!("Got continuation, bailing");
return;
}
Message::Pong(_) => {
AggregatedMessage::Pong(_) => {
*alive.lock().await = Instant::now();
}
_ => (),
};
}
let _ = session.close(None).await;
});
info!("Spawned");
tracing::info!("Spawned");
Ok(response)
}
async fn index() -> HttpResponse {
let s = r#"
<html>
<head>
<meta charset="utf-8" />
<title>Chat</title>
<script>
function onLoad() {
console.log("BOOTING");
const socket = new WebSocket("ws://localhost:8080/ws");
const input = document.getElementById("chat-input");
const logs = document.getElementById("chat-logs");
if (!input || !logs) {
alert("Couldn't find required elements");
console.err("Couldn't find required elements");
return;
async fn index() -> impl Responder {
Html::new(include_str!("chat.html").to_owned())
}
input.addEventListener("keyup", event => {
if (event.isComposing) {
return;
}
if (event.key != "Enter") {
return;
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> io::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
.init();
socket.send(input.value);
input.value = "";
}, false);
socket.onmessage = event => {
const newNode = document.createElement("li");
newNode.textContent = event.data;
let firstChild = null;
for (const n of logs.childNodes.values()) {
if (n.nodeType == 1) {
firstChild = n;
break;
}
}
if (firstChild) {
logs.insertBefore(newNode, firstChild);
} else {
logs.appendChild(newNode);
}
};
window.addEventListener("beforeunload", () => { socket.close() });
}
if (document.readyState === "complete") {
onLoad();
} else {
document.addEventListener("DOMContentLoaded", onLoad, false);
}
</script>
</head>
<body>
<input id="chat-input" type="test" />
<ul id="chat-logs">
</ul>
</body>
</html>
"#;
HttpResponse::Ok().content_type("text/html").body(s)
}
#[actix_rt::main]
async fn main() -> Result<(), anyhow::Error> {
std::env::set_var("RUST_LOG", "info");
pretty_env_logger::init();
let chat = Chat::new();
HttpServer::new(move || {

216
actix-ws/src/aggregated.rs Normal file
View File

@ -0,0 +1,216 @@
//! WebSocket stream for aggregating continuation frames.
use std::{
future::poll_fn,
io, mem,
pin::Pin,
task::{ready, Context, Poll},
};
use actix_http::ws::{CloseReason, Item, Message, ProtocolError};
use actix_web::web::{Bytes, BytesMut};
use bytestring::ByteString;
use futures_core::Stream;
use crate::MessageStream;
pub(crate) enum ContinuationKind {
Text,
Binary,
}
/// WebSocket message with any continuations aggregated together.
#[derive(Debug, PartialEq, Eq)]
pub enum AggregatedMessage {
/// Text message.
Text(ByteString),
/// Binary message.
Binary(Bytes),
/// Ping message.
Ping(Bytes),
/// Pong message.
Pong(Bytes),
/// Close message with optional reason.
Close(Option<CloseReason>),
}
/// Stream of messages from a WebSocket client, with continuations aggregated.
pub struct AggregatedMessageStream {
stream: MessageStream,
current_size: usize,
max_size: usize,
continuations: Vec<Bytes>,
continuation_kind: ContinuationKind,
}
impl AggregatedMessageStream {
#[must_use]
pub(crate) fn new(stream: MessageStream) -> Self {
AggregatedMessageStream {
stream,
current_size: 0,
max_size: 1024 * 1024,
continuations: Vec::new(),
continuation_kind: ContinuationKind::Binary,
}
}
/// Sets the maximum allowed size for aggregated continuations, in bytes.
///
/// By default, up to 1 MiB is allowed.
///
/// ```no_run
/// # use actix_ws::AggregatedMessageStream;
/// # async fn test(stream: AggregatedMessageStream) {
/// // increase the allowed size from 1MB to 8MB
/// let mut stream = stream.max_continuation_size(8 * 1024 * 1024);
///
/// while let Some(Ok(msg)) = stream.recv().await {
/// // handle message
/// }
/// # }
/// ```
#[must_use]
pub fn max_continuation_size(mut self, max_size: usize) -> Self {
self.max_size = max_size;
self
}
/// Waits for the next item from the aggregated message stream.
///
/// This is a convenience for calling the [`Stream`](Stream::poll_next()) implementation.
///
/// ```no_run
/// # use actix_ws::AggregatedMessageStream;
/// # async fn test(mut stream: AggregatedMessageStream) {
/// while let Some(Ok(msg)) = stream.recv().await {
/// // handle message
/// }
/// # }
/// ```
#[must_use]
pub async fn recv(&mut self) -> Option<<Self as Stream>::Item> {
poll_fn(|cx| Pin::new(&mut *self).poll_next(cx)).await
}
}
fn size_error() -> Poll<Option<Result<AggregatedMessage, ProtocolError>>> {
Poll::Ready(Some(Err(ProtocolError::Io(io::Error::other(
"Exceeded maximum continuation size",
)))))
}
impl Stream for AggregatedMessageStream {
type Item = Result<AggregatedMessage, ProtocolError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
let Some(msg) = ready!(Pin::new(&mut this.stream).poll_next(cx)?) else {
return Poll::Ready(None);
};
match msg {
Message::Continuation(item) => match item {
Item::FirstText(bytes) => {
this.continuation_kind = ContinuationKind::Text;
this.current_size += bytes.len();
if this.current_size > this.max_size {
this.continuations.clear();
return size_error();
}
this.continuations.push(bytes);
Poll::Pending
}
Item::FirstBinary(bytes) => {
this.continuation_kind = ContinuationKind::Binary;
this.current_size += bytes.len();
if this.current_size > this.max_size {
this.continuations.clear();
return size_error();
}
this.continuations.push(bytes);
Poll::Pending
}
Item::Continue(bytes) => {
this.current_size += bytes.len();
if this.current_size > this.max_size {
this.continuations.clear();
return size_error();
}
this.continuations.push(bytes);
Poll::Pending
}
Item::Last(bytes) => {
this.current_size += bytes.len();
if this.current_size > this.max_size {
// reset current_size, as this is the last message for
// the current continuation
this.current_size = 0;
this.continuations.clear();
return size_error();
}
this.continuations.push(bytes);
let bytes = collect(&mut this.continuations);
this.current_size = 0;
match this.continuation_kind {
ContinuationKind::Text => {
Poll::Ready(Some(match ByteString::try_from(bytes) {
Ok(bytestring) => Ok(AggregatedMessage::Text(bytestring)),
Err(err) => Err(ProtocolError::Io(io::Error::new(
io::ErrorKind::InvalidData,
err.to_string(),
))),
}))
}
ContinuationKind::Binary => {
Poll::Ready(Some(Ok(AggregatedMessage::Binary(bytes))))
}
}
}
},
Message::Text(text) => Poll::Ready(Some(Ok(AggregatedMessage::Text(text)))),
Message::Binary(binary) => Poll::Ready(Some(Ok(AggregatedMessage::Binary(binary)))),
Message::Ping(ping) => Poll::Ready(Some(Ok(AggregatedMessage::Ping(ping)))),
Message::Pong(pong) => Poll::Ready(Some(Ok(AggregatedMessage::Pong(pong)))),
Message::Close(close) => Poll::Ready(Some(Ok(AggregatedMessage::Close(close)))),
Message::Nop => unreachable!("MessageStream should not produce no-ops"),
}
}
}
fn collect(continuations: &mut Vec<Bytes>) -> Bytes {
let continuations = mem::take(continuations);
let total_len = continuations.iter().map(|b| b.len()).sum();
let mut buf = BytesMut::with_capacity(total_len);
for chunk in continuations {
buf.extend(chunk);
}
buf.freeze()
}

View File

@ -2,13 +2,12 @@
//!
//! For usage, see documentation on [`handle()`].
#![deny(rust_2018_idioms, nonstandard_style, future_incompatible)]
#![warn(missing_docs)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub use actix_http::ws::{CloseCode, CloseReason, Message, ProtocolError};
pub use actix_http::ws::{CloseCode, CloseReason, Item, Message, ProtocolError};
use actix_http::{
body::{BodyStream, MessageBody},
ws::handshake,
@ -16,25 +15,28 @@ use actix_http::{
use actix_web::{web, HttpRequest, HttpResponse};
use tokio::sync::mpsc::channel;
mod fut;
mod aggregated;
mod session;
mod stream;
pub use self::{
fut::{MessageStream, StreamingBody},
aggregated::{AggregatedMessage, AggregatedMessageStream},
session::{Closed, Session},
stream::{MessageStream, StreamingBody},
};
/// Begin handling websocket traffic
///
/// ```no_run
/// use actix_web::{middleware::Logger, web, App, Error, HttpRequest, HttpResponse, HttpServer};
/// use std::io;
/// use actix_web::{middleware::Logger, web, App, HttpRequest, HttpServer, Responder};
/// use actix_ws::Message;
/// use futures::stream::StreamExt as _;
/// use futures_util::StreamExt as _;
///
/// async fn ws(req: HttpRequest, body: web::Payload) -> Result<HttpResponse, Error> {
/// async fn ws(req: HttpRequest, body: web::Payload) -> actix_web::Result<impl Responder> {
/// let (response, mut session, mut msg_stream) = actix_ws::handle(&req, body)?;
///
/// actix_rt::spawn(async move {
/// actix_web::rt::spawn(async move {
/// while let Some(Ok(msg)) = msg_stream.next().await {
/// match msg {
/// Message::Ping(bytes) => {
@ -42,7 +44,8 @@ pub use self::{
/// return;
/// }
/// }
/// Message::Text(s) => println!("Got text, {}", s),
///
/// Message::Text(msg) => println!("Got text: {msg}"),
/// _ => break,
/// }
/// }
@ -53,18 +56,16 @@ pub use self::{
/// Ok(response)
/// }
///
/// #[actix_rt::main]
/// async fn main() -> Result<(), anyhow::Error> {
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() -> io::Result<()> {
/// HttpServer::new(move || {
/// App::new()
/// .wrap(Logger::default())
/// .route("/ws", web::get().to(ws))
/// .wrap(Logger::default())
/// })
/// .bind("127.0.0.1:8080")?
/// .bind(("127.0.0.1", 8080))?
/// .run()
/// .await?;
///
/// Ok(())
/// .await
/// }
/// ```
pub fn handle(

View File

@ -1,16 +1,19 @@
use std::sync::{
use std::{
fmt,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use actix_http::ws::{CloseReason, Message};
use actix_http::ws::{CloseReason, Item, Message};
use actix_web::web::Bytes;
use bytestring::ByteString;
use tokio::sync::mpsc::Sender;
/// A handle into the websocket session.
///
/// This type can be used to send messages into the websocket.
/// This type can be used to send messages into the WebSocket.
#[derive(Clone)]
pub struct Session {
inner: Option<Sender<Message>>,
@ -21,9 +24,9 @@ pub struct Session {
#[derive(Debug)]
pub struct Closed;
impl std::fmt::Display for Closed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Session is closed")
impl fmt::Display for Closed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Session is closed")
}
}
@ -43,12 +46,15 @@ impl Session {
}
}
/// Send text into the websocket
/// Sends text into the WebSocket.
///
/// ```rust,ignore
/// ```no_run
/// # use actix_ws::Session;
/// # async fn test(mut session: Session) {
/// if session.text("Some text").await.is_err() {
/// // session closed
/// }
/// # }
/// ```
pub async fn text(&mut self, msg: impl Into<ByteString>) -> Result<(), Closed> {
self.pre_check();
@ -62,12 +68,15 @@ impl Session {
}
}
/// Send raw bytes into the websocket
/// Sends raw bytes into the WebSocket.
///
/// ```rust,ignore
/// if session.binary(b"some bytes").await.is_err() {
/// ```no_run
/// # use actix_ws::Session;
/// # async fn test(mut session: Session) {
/// if session.binary(&b"some bytes"[..]).await.is_err() {
/// // session closed
/// }
/// # }
/// ```
pub async fn binary(&mut self, msg: impl Into<Bytes>) -> Result<(), Closed> {
self.pre_check();
@ -81,15 +90,18 @@ impl Session {
}
}
/// Ping the client
/// Pings the client.
///
/// For many applications, it will be important to send regular pings to keep track of if the
/// client has disconnected
///
/// ```rust,ignore
/// ```no_run
/// # use actix_ws::Session;
/// # async fn test(mut session: Session) {
/// if session.ping(b"").await.is_err() {
/// // session is closed
/// }
/// # }
/// ```
pub async fn ping(&mut self, msg: &[u8]) -> Result<(), Closed> {
self.pre_check();
@ -103,15 +115,18 @@ impl Session {
}
}
/// Pong the client
/// Pongs the client.
///
/// ```rust,ignore
/// ```no_run
/// # use actix_ws::{Message, Session};
/// # async fn test(mut session: Session, msg: Message) {
/// match msg {
/// Message::Ping(bytes) => {
/// let _ = session.pong(&bytes).await;
/// }
/// _ => (),
/// }
/// # }
pub async fn pong(&mut self, msg: &[u8]) -> Result<(), Closed> {
self.pre_check();
if let Some(inner) = self.inner.as_mut() {
@ -124,15 +139,51 @@ impl Session {
}
}
/// Send a close message, and consume the session
/// Manually controls sending continuations.
///
/// All clones will return `Err(Closed)` if used after this call
/// Be wary of this method. Continuations represent multiple frames that, when combined, are
/// presented as a single message. They are useful when the entire contents of a message are
/// not available all at once. However, continuations MUST NOT be interrupted by other Text or
/// Binary messages. Control messages such as Ping, Pong, or Close are allowed to interrupt a
/// continuation.
///
/// ```rust,ignore
/// Continuations must be initialized with a First variant, and must be terminated by a Last
/// variant, with only Continue variants sent in between.
///
/// ```no_run
/// # use actix_ws::{Item, Session};
/// # async fn test(mut session: Session) -> Result<(), Box<dyn std::error::Error>> {
/// session.continuation(Item::FirstText("Hello".into())).await?;
/// session.continuation(Item::Continue(b", World"[..].into())).await?;
/// session.continuation(Item::Last(b"!"[..].into())).await?;
/// # Ok(())
/// # }
/// ```
pub async fn continuation(&mut self, msg: Item) -> Result<(), Closed> {
self.pre_check();
if let Some(inner) = self.inner.as_mut() {
inner
.send(Message::Continuation(msg))
.await
.map_err(|_| Closed)
} else {
Err(Closed)
}
}
/// Sends a close message, and consumes the session.
///
/// All clones will return `Err(Closed)` if used after this call.
///
/// ```no_run
/// # use actix_ws::{Closed, Session};
/// # async fn test(mut session: Session) -> Result<(), Closed> {
/// session.close(None).await
/// # }
/// ```
pub async fn close(mut self, reason: Option<CloseReason>) -> Result<(), Closed> {
self.pre_check();
if let Some(inner) = self.inner.take() {
self.closed.store(true, Ordering::Relaxed);
inner.send(Message::Close(reason)).await.map_err(|_| Closed)

View File

@ -1,7 +1,7 @@
use std::{
collections::VecDeque,
future::poll_fn,
io,
io, mem,
pin::Pin,
task::{Context, Poll},
};
@ -15,25 +15,15 @@ use actix_web::{
web::{Bytes, BytesMut},
Error,
};
use bytestring::ByteString;
use futures_core::stream::Stream;
use tokio::sync::mpsc::Receiver;
/// A response body for Websocket HTTP Requests
use crate::AggregatedMessageStream;
/// Response body for a WebSocket.
pub struct StreamingBody {
session_rx: Receiver<Message>,
messages: VecDeque<Message>,
buf: BytesMut,
codec: Codec,
closing: bool,
}
/// A stream of Messages from a websocket client
///
/// Messages can be accessed via the stream's `.next()` method
pub struct MessageStream {
payload: Payload,
messages: VecDeque<Message>,
buf: BytesMut,
codec: Codec,
@ -52,6 +42,16 @@ impl StreamingBody {
}
}
/// Stream of messages from a WebSocket client.
pub struct MessageStream {
payload: Payload,
messages: VecDeque<Message>,
buf: BytesMut,
codec: Codec,
closing: bool,
}
impl MessageStream {
pub(super) fn new(payload: Payload) -> Self {
MessageStream {
@ -63,13 +63,50 @@ impl MessageStream {
}
}
/// Wait for the next item from the message stream
/// Sets the maximum permitted size for received WebSocket frames, in bytes.
///
/// ```rust,ignore
/// By default, up to 64KiB is allowed.
///
/// Any received frames larger than the permitted value will return
/// `Err(ProtocolError::Overflow)` instead.
///
/// ```no_run
/// # use actix_ws::MessageStream;
/// # fn test(stream: MessageStream) {
/// // increase permitted frame size from 64KB to 1MB
/// let stream = stream.max_frame_size(1024 * 1024);
/// # }
/// ```
#[must_use]
pub fn max_frame_size(mut self, max_size: usize) -> Self {
self.codec = self.codec.max_size(max_size);
self
}
/// Returns a stream wrapper that collects continuation frames into their equivalent aggregated
/// forms, i.e., binary or text.
///
/// By default, continuations will be aggregated up to 1MiB in size (customizable with
/// [`AggregatedMessageStream::max_continuation_size()`]). The stream implementation returns an
/// error if this size is exceeded.
#[must_use]
pub fn aggregate_continuations(self) -> AggregatedMessageStream {
AggregatedMessageStream::new(self)
}
/// Waits for the next item from the message stream
///
/// This is a convenience for calling the [`Stream`](Stream::poll_next()) implementation.
///
/// ```no_run
/// # use actix_ws::MessageStream;
/// # async fn test(mut stream: MessageStream) {
/// while let Some(Ok(msg)) = stream.recv().await {
/// // handle message
/// }
/// # }
/// ```
#[must_use]
pub async fn recv(&mut self) -> Option<Result<Message, ProtocolError>> {
poll_fn(|cx| Pin::new(&mut *self).poll_next(cx)).await
}
@ -99,13 +136,17 @@ impl Stream for StreamingBody {
}
while let Some(msg) = this.messages.pop_front() {
if let Err(e) = this.codec.encode(msg, &mut this.buf) {
return Poll::Ready(Some(Err(e.into())));
if let Err(err) = this.codec.encode(msg, &mut this.buf) {
return Poll::Ready(Some(Err(err.into())));
}
}
if !this.buf.is_empty() {
return Poll::Ready(Some(Ok(this.buf.split().freeze())));
return Poll::Ready(Some(Ok(mem::take(&mut this.buf).freeze())));
}
if this.closing {
return Poll::Ready(None);
}
Poll::Pending
@ -132,11 +173,8 @@ impl Stream for MessageStream {
Poll::Ready(Some(Ok(bytes))) => {
this.buf.extend_from_slice(&bytes);
}
Poll::Ready(Some(Err(e))) => {
return Poll::Ready(Some(Err(ProtocolError::Io(io::Error::new(
io::ErrorKind::Other,
e.to_string(),
)))));
Poll::Ready(Some(Err(err))) => {
return Poll::Ready(Some(Err(ProtocolError::Io(io::Error::other(err)))));
}
Poll::Ready(None) => {
this.closing = true;
@ -151,12 +189,11 @@ impl Stream for MessageStream {
while let Some(frame) = this.codec.decode(&mut this.buf)? {
let message = match frame {
Frame::Text(bytes) => {
let s = std::str::from_utf8(&bytes)
.map_err(|e| {
ProtocolError::Io(io::Error::new(io::ErrorKind::Other, e.to_string()))
ByteString::try_from(bytes)
.map(Message::Text)
.map_err(|err| {
ProtocolError::Io(io::Error::new(io::ErrorKind::InvalidData, err))
})?
.to_string();
Message::Text(s.into())
}
Frame::Binary(bytes) => Message::Binary(bytes),
Frame::Ping(bytes) => Message::Ping(bytes),

109
justfile
View File

@ -1,52 +1,135 @@
# depends on:
# - https://crates.io/crates/fd-find
# - https://crates.io/crates/cargo-check-external-types
_list:
@just --list
# Format workspace.
toolchain := ""
msrv := ```
cargo metadata --format-version=1 \
| jq -r 'first(.packages[] | select(.source == null and .rust_version)) | .rust_version' \
| sed -E 's/^1\.([0-9]{2})$/1\.\1\.0/'
```
msrv_rustup := "+" + msrv
# Run Clippy over workspace.
[group("lint")]
clippy:
cargo {{ toolchain }} clippy --workspace --all-targets --all-features
# Format project.
[group("lint")]
fmt: update-readmes
cargo +nightly fmt
npx -y prettier --write $(fd --hidden --extension=yml --extension=md)
fd --type=file --hidden --extension=yml --extension=md --exec-batch npx -y prettier --write
# Check project.
[group("lint")]
check:
cargo +nightly fmt -- --check
fd --type=file --hidden --extension=yml --extension=md --exec-batch npx -y prettier --check
# Update READMEs from crate root documentation.
[group("lint")]
update-readmes:
cd ./actix-cors && cargo rdme --force
cd ./actix-session && cargo rdme --force
cd ./actix-identity && cargo rdme --force
npx -y prettier --write $(fd README.md)
cd ./actix-session && cargo rdme --force
fd README.md --exec-batch npx -y prettier --write
# Test workspace code.
[group("test")]
test:
cargo {{ toolchain }} nextest run --workspace --all-features
cargo {{ toolchain }} test --doc --workspace --all-features
# Downgrade dev-dependencies necessary to run MSRV checks/tests.
[private]
downgrade-for-msrv:
cargo update -p=native-tls --precise=0.2.13
cargo update -p=litemap --precise=0.7.4
cargo update -p=zerofrom --precise=0.1.5
# Test workspace using MSRV.
[group("test")]
test-msrv:
@just downgrade-for-msrv
@just toolchain={{ msrv_rustup }} test
# Test workspace code and docs.
[group("test")]
test-all: test test-docs
# Test workspace and collect coverage info.
[private]
test-coverage:
cargo {{ toolchain }} llvm-cov nextest --no-report --all-features
cargo {{ toolchain }} llvm-cov --doc --no-report --all-features
# Test workspace and generate Codecov report.
test-coverage-codecov: test-coverage
cargo {{ toolchain }} llvm-cov report --doctests --codecov --output-path=codecov.json
# Test workspace and generate LCOV report.
test-coverage-lcov: test-coverage
cargo {{ toolchain }} llvm-cov report --doctests --lcov --output-path=lcov.info
# Test workspace docs.
[group("test")]
[group("docs")]
test-docs:
cargo {{ toolchain }} test --doc --workspace --all-features --no-fail-fast -- --nocapture
# Document crates in workspace.
doc:
RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --all-features
[group("docs")]
doc *args: && doc-set-workspace-crates
rm -f "$(cargo metadata --format-version=1 | jq -r '.target_directory')/doc/crates.js"
RUSTDOCFLAGS="--cfg=docsrs -Dwarnings" cargo +nightly doc --workspace --all-features {{ args }}
[group("docs")]
[private]
doc-set-workspace-crates:
#!/usr/bin/env bash
(
echo "window.ALL_CRATES = "
cargo metadata --format-version=1 \
| jq '[.packages[] | select(.source == null) | .targets | map(select(.doc) | .name)] | flatten'
echo ";"
) > "$(cargo metadata --format-version=1 | jq -r '.target_directory')/doc/crates.js"
# Document crates in workspace and watch for changes.
[group("docs")]
doc-watch:
RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --all-features --open
cargo watch -- RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --all-features
@just doc --open
cargo watch -- just doc
# Check for unintentional external type exposure on all crates in workspace.
check-external-types-all toolchain="+nightly":
[group("lint")]
check-external-types-all:
#!/usr/bin/env bash
set -euo pipefail
exit=0
for f in $(find . -mindepth 2 -maxdepth 2 -name Cargo.toml | grep -vE "\-codegen/|\-derive/|\-macros/"); do
if ! just check-external-types-manifest "$f" {{toolchain}}; then exit=1; fi
if ! just toolchain={{ toolchain }} check-external-types-manifest "$f"; then exit=1; fi
echo
echo
done
exit $exit
# Check for unintentional external type exposure on all crates in workspace.
check-external-types-all-table toolchain="+nightly":
[group("lint")]
check-external-types-all-table:
#!/usr/bin/env bash
set -euo pipefail
for f in $(find . -mindepth 2 -maxdepth 2 -name Cargo.toml | grep -vE "\-codegen/|\-derive/|\-macros/"); do
echo
echo "Checking for $f"
just check-external-types-manifest "$f" {{toolchain}} --output-format=markdown-table
just toolchain={{ toolchain }} check-external-types-manifest "$f" --output-format=markdown-table
done
# Check for unintentional external type exposure on a crate.
check-external-types-manifest manifest_path toolchain="+nightly" *extra_args="":
[group("lint")]
check-external-types-manifest manifest_path *extra_args="":
cargo {{ toolchain }} check-external-types --manifest-path "{{ manifest_path }}" {{ extra_args }}

46598
lcov.info Normal file

File diff suppressed because it is too large Load Diff