1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-02-22 18:33:18 +01:00

Merge branch 'master' into master

This commit is contained in:
Raphael C 2023-11-09 13:00:47 +01:00 committed by GitHub
commit bedc1353c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 959 additions and 159 deletions

View File

@ -35,7 +35,7 @@ jobs:
with: with:
toolchain: nightly toolchain: nightly
- uses: taiki-e/install-action@v2.20.3 - uses: taiki-e/install-action@v2.21.7
with: with:
tool: cargo-hack tool: cargo-hack
@ -77,7 +77,7 @@ jobs:
with: with:
toolchain: nightly toolchain: nightly
- uses: taiki-e/install-action@v2.20.3 - uses: taiki-e/install-action@v2.21.7
with: with:
tool: cargo-hack tool: cargo-hack

View File

@ -49,7 +49,7 @@ jobs:
toolchain: ${{ matrix.version.version }} toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack - name: Install cargo-hack
uses: taiki-e/install-action@v2.20.3 uses: taiki-e/install-action@v2.21.7
with: with:
tool: cargo-hack tool: cargo-hack
@ -100,7 +100,7 @@ jobs:
toolchain: ${{ matrix.version.version }} toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack - name: Install cargo-hack
uses: taiki-e/install-action@v2.20.3 uses: taiki-e/install-action@v2.21.7
with: with:
tool: cargo-hack tool: cargo-hack

View File

@ -1,9 +1,11 @@
name: Upload Documentation name: Upload Documentation
on: on:
push: { branches: [master] } push:
branches: [master]
permissions: { contents: write } permissions:
contents: write
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@ -18,10 +20,11 @@ jobs:
- name: Install Rust (nightly) - name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.5.0 uses: actions-rust-lang/setup-rust-toolchain@v1.5.0
with: { toolchain: nightly } with:
toolchain: nightly
- name: Build Docs - name: Build Docs
run: cargo doc --workspace --all-features --no-deps run: cargo doc --no-deps --workspace --all-features
- name: Tweak HTML - name: Tweak HTML
run: echo '<meta http-equiv="refresh" content="0;url=actix_cors/index.html">' > target/doc/index.html run: echo '<meta http-equiv="refresh" content="0;url=actix_cors/index.html">' > target/doc/index.html
@ -29,6 +32,5 @@ jobs:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4.4.3 uses: JamesIves/github-pages-deploy-action@v4.4.3
with: with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} folder: target/doc
BRANCH: gh-pages branch: gh-pages
FOLDER: target/doc

View File

@ -9,6 +9,7 @@ members = [
"actix-session", "actix-session",
"actix-settings", "actix-settings",
"actix-web-httpauth", "actix-web-httpauth",
"actix-ws",
] ]
[workspace.package] [workspace.package]

View File

@ -186,8 +186,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2017-NOW Nikolay Kim Copyright 2017-NOW Actix team
Copyright 2017-NOW svartalf and Actix team
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -1,5 +1,4 @@
Copyright (c) 2017 Nikolay Kim Copyright (c) 2023 Actix team
Copyright (c) 2017 svartalf and Actix team
Permission is hereby granted, free of charge, to any Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated person obtaining a copy of this software and associated

View File

@ -10,15 +10,16 @@
## Crates by @actix ## Crates by @actix
| Crate | | | | Crate | | |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| [actix-cors] | [![crates.io](https://img.shields.io/crates/v/actix-cors?label=latest)](https://crates.io/crates/actix-cors) [![dependency status](https://deps.rs/crate/actix-cors/0.6.4/status.svg)](https://deps.rs/crate/actix-cors/0.6.4) | Cross-Origin Resource Sharing (CORS) controls. | | [actix-cors] | [![crates.io](https://img.shields.io/crates/v/actix-cors?label=latest)](https://crates.io/crates/actix-cors) [![dependency status](https://deps.rs/crate/actix-cors/latest/status.svg)](https://deps.rs/crate/actix-cors) | Cross-Origin Resource Sharing (CORS) controls. |
| [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/0.6.0/status.svg)](https://deps.rs/crate/actix-identity/0.6.0) | Identity management. | | [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/0.5.1/status.svg)](https://deps.rs/crate/actix-limitation/0.5.1) | Rate-limiting using a fixed window counter for arbitrary keys, backed by Redis. | | [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/0.10.0/status.svg)](https://deps.rs/crate/actix-protobuf/0.10.0) | Protobuf payload extractor. | | [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/0.13.0/status.svg)](https://deps.rs/crate/actix-redis/0.13.0) | Actor-based Redis client. | | [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/0.8.0/status.svg)](https://deps.rs/crate/actix-session/0.8.0) | Session management. | | [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/0.6.0/status.svg)](https://deps.rs/crate/actix-settings/0.6.0) | Easily manage Actix Web's settings from a TOML file and environment variables. | | [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/0.8.1/status.svg)](https://deps.rs/crate/actix-web-httpauth/0.8.1) | HTTP authentication schemes. | | [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. |
| [actix-ws] | [![crates.io](https://img.shields.io/crates/v/actix-ws?label=latest)][actix-ws] [![dependency status](https://deps.rs/crate/actix-ws/latest/status.svg)](https://deps.rs/crate/actix-ws) | WebSockets for Actix Web, without actors. |
--- ---
@ -27,23 +28,22 @@
These crates are provided by the community. These crates are provided by the community.
| Crate | | | | 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/0.19.1/status.svg)](https://deps.rs/crate/actix-web-lab/0.19.1) | Experimental extractors, middleware, and other extras for possible inclusion in Actix Web. | | [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/0.1.5/status.svg)](https://deps.rs/crate/actix-multipart-extract/0.1.5) | Better multipart form support for 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/0.7.0-beta.4/status.svg)](https://deps.rs/crate/actix-form-data/0.7.0-beta.4) | 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/0.5.1/status.svg)](https://deps.rs/crate/actix-governor/0.5.1) | Rate-limiting backed by governor. | | [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/0.4.2/status.svg)](https://deps.rs/crate/actix-casbin/0.4.2) | Authorization library that supports access control models like ACL, RBAC & ABAC. | | [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/0.3.1/status.svg)](https://deps.rs/crate/actix-ip-filter/0.3.1) | IP address filter. Supports glob patterns. | | [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. |
| [actix-web-static-files] | [![crates.io](https://img.shields.io/crates/v/actix-web-static-files?label=latest)][actix-web-static-files] [![dependency status](https://deps.rs/crate/actix-web-static-files/4.0.1/status.svg)](https://deps.rs/crate/actix-web-static-files/4.0.1) | Static files as embedded resources. | | [actix-web-static-files] | [![crates.io](https://img.shields.io/crates/v/actix-web-static-files?label=latest)][actix-web-static-files] [![dependency status](https://deps.rs/crate/actix-web-static-files/latest/status.svg)](https://deps.rs/crate/actix-web-static-files) | Static files as embedded resources. |
| [actix-web-grants] | [![crates.io](https://img.shields.io/crates/v/actix-web-grants?label=latest)][actix-web-grants] [![dependency status](https://deps.rs/crate/actix-web-grants/3.0.2/status.svg)](https://deps.rs/crate/actix-web-grants/3.0.2) | Extension for validating user authorities. | | [actix-web-grants] | [![crates.io](https://img.shields.io/crates/v/actix-web-grants?label=latest)][actix-web-grants] [![dependency status](https://deps.rs/crate/actix-web-grants/latest/status.svg)](https://deps.rs/crate/actix-web-grants) | Extension for validating user authorities. |
| [aliri_actix] | [![crates.io](https://img.shields.io/crates/v/aliri_actix?label=latest)][aliri_actix] [![dependency status](https://deps.rs/crate/aliri_actix/0.9.0/status.svg)](https://deps.rs/crate/aliri_actix/0.9.0) | Endpoint authorization and authentication using scoped OAuth2 JWT tokens. | | [aliri_actix] | [![crates.io](https://img.shields.io/crates/v/aliri_actix?label=latest)][aliri_actix] [![dependency status](https://deps.rs/crate/aliri_actix/latest/status.svg)](https://deps.rs/crate/aliri_actix) | Endpoint authorization and authentication using scoped OAuth2 JWT tokens. |
| [actix-web-flash-messages] | [![crates.io](https://img.shields.io/crates/v/actix-web-flash-messages?label=latest)][actix-web-flash-messages] [![dependency status](https://deps.rs/crate/actix-web-flash-messages/0.4.2/status.svg)](https://deps.rs/crate/actix-web-flash-messages/0.4.2) | Support for flash messages/one-time notifications in `actix-web`. | | [actix-web-flash-messages] | [![crates.io](https://img.shields.io/crates/v/actix-web-flash-messages?label=latest)][actix-web-flash-messages] [![dependency status](https://deps.rs/crate/actix-web-flash-messages/latest/status.svg)](https://deps.rs/crate/actix-web-flash-messages) | Support for flash messages/one-time notifications in `actix-web`. |
| [awmp] | [![crates.io](https://img.shields.io/crates/v/awmp?label=latest)][awmp] [![dependency status](https://deps.rs/crate/awmp/0.8.1/status.svg)](https://deps.rs/crate/awmp/0.8.1) | An easy to use wrapper around multipart fields for Actix Web. | | [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/0.7.6/status.svg)](https://deps.rs/crate/tracing-actix-web/0.7.6) | A middleware to collect telemetry data from applications built on top of the Actix Web framework. | | [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-ws] | [![crates.io](https://img.shields.io/crates/v/actix-ws?label=latest)][actix-ws] [![dependency status](https://deps.rs/crate/actix-ws/0.2.5/status.svg)](https://deps.rs/crate/actix-ws/0.2.5) | Actor-less WebSockets for the Actix Runtime. | | [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-hash] | [![crates.io](https://img.shields.io/crates/v/actix-hash?label=latest)][actix-hash] [![dependency status](https://deps.rs/crate/actix-hash/0.5.0/status.svg)](https://deps.rs/crate/actix-hash/0.5.0) | 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 |
| [actix-bincode] | ![crates.io](https://img.shields.io/crates/v/actix-bincode?label=latest) [![dependency status](https://deps.rs/crate/actix-bincode/0.2.2/status.svg)](https://deps.rs/crate/actix-bincode/0.2.2) | 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 |
| [sentinel-actix] | ![crates.io](https://img.shields.io/crates/v/sentinel-actix?label=latest) [![dependency status](https://deps.rs/crate/sentinel-actix/0.1.0/status.svg)](https://deps.rs/crate/sentinel-actix/0.1.0) | General and flexible protection for Actix Web |
To add a crate to this list, submit a pull request. To add a crate to this list, submit a pull request.

View File

@ -52,13 +52,20 @@ static ALL_METHODS_SET: Lazy<HashSet<Method>> = Lazy::new(|| {
/// The alternative [`Cors::permissive()`] constructor is available for local development, allowing /// The alternative [`Cors::permissive()`] constructor is available for local development, allowing
/// all origins and headers, etc. **The permissive constructor should not be used in production.** /// all origins and headers, etc. **The permissive constructor should not be used in production.**
/// ///
/// # Behavior
///
/// In all cases, behavior for this crate follows the [Fetch Standard CORS protocol]. See that
/// document for information on exact semantics for configuration options and combinations.
///
/// # Errors /// # Errors
///
/// Errors surface in the middleware initialization phase. This means that, if you have logs enabled /// Errors surface in the middleware initialization phase. This means that, if you have logs enabled
/// in Actix Web (using `env_logger` or other crate that exposes logs from the `log` crate), error /// in Actix Web (using `env_logger` or other crate that exposes logs from the `log` crate), error
/// messages will outline what is wrong with the CORS configuration in the server logs and the /// messages will outline what is wrong with the CORS configuration in the server logs and the
/// server will fail to start up or serve requests. /// server will fail to start up or serve requests.
/// ///
/// # Example /// # Example
///
/// ``` /// ```
/// use actix_cors::Cors; /// use actix_cors::Cors;
/// use actix_web::http::header; /// use actix_web::http::header;
@ -72,6 +79,8 @@ static ALL_METHODS_SET: Lazy<HashSet<Method>> = Lazy::new(|| {
/// ///
/// // `cors` can now be used in `App::wrap`. /// // `cors` can now be used in `App::wrap`.
/// ``` /// ```
///
/// [Fetch Standard CORS protocol]: https://fetch.spec.whatwg.org/#http-cors-protocol
#[derive(Debug)] #[derive(Debug)]
pub struct Cors { pub struct Cors {
inner: Rc<Inner>, inner: Rc<Inner>,
@ -79,7 +88,8 @@ pub struct Cors {
} }
impl Cors { impl Cors {
/// A very permissive set of default for quick development. Not recommended for production use. /// Constructs a very permissive set of defaults for quick development. (Not recommended for
/// production use.)
/// ///
/// *All* origins, methods, request headers and exposed headers allowed. Credentials supported. /// *All* origins, methods, request headers and exposed headers allowed. Credentials supported.
/// Max age 1 hour. Does not send wildcard. /// Max age 1 hour. Does not send wildcard.
@ -124,7 +134,7 @@ impl Cors {
self self
} }
/// Add an origin that is allowed to make requests. /// Adds an origin that is allowed to make requests.
/// ///
/// This method allows specifying a finite set of origins to verify the value of the `Origin` /// This method allows specifying a finite set of origins to verify the value of the `Origin`
/// request header. These are `origin-or-null` types in the [Fetch Standard]. /// request header. These are `origin-or-null` types in the [Fetch Standard].
@ -177,7 +187,7 @@ impl Cors {
self self
} }
/// Determinate allowed origins by processing requests which didn't match any origins specified /// Determinates allowed origins by processing requests which didn't match any origins specified
/// in the `allowed_origin`. /// in the `allowed_origin`.
/// ///
/// The function will receive two parameters, the Origin header value, and the `RequestHead` of /// The function will receive two parameters, the Origin header value, and the `RequestHead` of
@ -209,14 +219,11 @@ impl Cors {
self self
} }
/// Set a list of methods which allowed origins can perform. /// Sets a list of methods which allowed origins can perform.
/// ///
/// These will be sent in the `Access-Control-Allow-Methods` response header as specified in /// These will be sent in the `Access-Control-Allow-Methods` response header.
/// the [Fetch Standard CORS protocol].
/// ///
/// This defaults to an empty set. /// This defaults to an empty set.
///
/// [Fetch Standard CORS protocol]: https://fetch.spec.whatwg.org/#http-cors-protocol
pub fn allowed_methods<U, M>(mut self, methods: U) -> Cors pub fn allowed_methods<U, M>(mut self, methods: U) -> Cors
where where
U: IntoIterator<Item = M>, U: IntoIterator<Item = M>,
@ -279,16 +286,13 @@ impl Cors {
self self
} }
/// Set a list of request header field names which can be used when this resource is accessed by /// Sets a list of request header field names which can be used when this resource is accessed
/// allowed origins. /// by allowed origins.
/// ///
/// If `All` is set, whatever is requested by the client in `Access-Control-Request-Headers` /// If `All` is set, whatever is requested by the client in `Access-Control-Request-Headers`
/// will be echoed back in the `Access-Control-Allow-Headers` header as specified in /// will be echoed back in the `Access-Control-Allow-Headers` header.
/// the [Fetch Standard CORS protocol].
/// ///
/// This defaults to an empty set. /// This defaults to an empty set.
///
/// [Fetch Standard CORS protocol]: https://fetch.spec.whatwg.org/#http-cors-protocol
pub fn allowed_headers<U, H>(mut self, headers: U) -> Cors pub fn allowed_headers<U, H>(mut self, headers: U) -> Cors
where where
U: IntoIterator<Item = H>, U: IntoIterator<Item = H>,
@ -329,13 +333,11 @@ impl Cors {
self self
} }
/// Set a list of headers which are safe to expose to the API of a CORS API specification. /// Sets a list of headers which are safe to expose to the API of a CORS API specification.
/// This corresponds to the `Access-Control-Expose-Headers` response header as specified in ///
/// the [Fetch Standard CORS protocol]. /// This corresponds to the `Access-Control-Expose-Headers` response header.
/// ///
/// This defaults to an empty set. /// This defaults to an empty set.
///
/// [Fetch Standard CORS protocol]: https://fetch.spec.whatwg.org/#http-cors-protocol
pub fn expose_headers<U, H>(mut self, headers: U) -> Cors pub fn expose_headers<U, H>(mut self, headers: U) -> Cors
where where
U: IntoIterator<Item = H>, U: IntoIterator<Item = H>,
@ -364,12 +366,11 @@ impl Cors {
self self
} }
/// Set a maximum time (in seconds) for which this CORS request may be cached. This value is set /// Sets a maximum time (in seconds) for which this CORS request may be cached.
/// as the `Access-Control-Max-Age` header as specified in the [Fetch Standard CORS protocol]. ///
/// This value is set as the `Access-Control-Max-Age` header.
/// ///
/// Pass a number (of seconds) or use None to disable sending max age header. /// Pass a number (of seconds) or use None to disable sending max age header.
///
/// [Fetch Standard CORS protocol]: https://fetch.spec.whatwg.org/#http-cors-protocol
pub fn max_age(mut self, max_age: impl Into<Option<usize>>) -> Cors { pub fn max_age(mut self, max_age: impl Into<Option<usize>>) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) { if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.max_age = max_age.into(); cors.max_age = max_age.into();
@ -378,17 +379,17 @@ impl Cors {
self self
} }
/// Set to use wildcard origins. /// Configures use of wildcard (`*`) origin in responses when appropriate.
/// ///
/// If send wildcard is set and the `allowed_origins` parameter is `All`, a wildcard /// If send wildcard is set and the `allowed_origins` parameter is `All`, a wildcard
/// `Access-Control-Allow-Origin` response header is sent, rather than the requests /// `Access-Control-Allow-Origin` response header is sent, rather than the requests
/// `Origin` header. /// `Origin` header.
/// ///
/// This **CANNOT** be used in conjunction with `allowed_origins` set to `All` and /// This option **CANNOT** be used in conjunction with a [credential
/// `allow_credentials` set to `true`. Depending on the mode of usage, this will either result /// supported](Self::supports_credentials()) configuration. Doing so will result in an error
/// in an `CorsError::CredentialsWithWildcardOrigin` error during actix launch or runtime. /// during server startup.
/// ///
/// Defaults to `false`. /// Defaults to disabled.
pub fn send_wildcard(mut self) -> Cors { pub fn send_wildcard(mut self) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) { if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.send_wildcard = true; cors.send_wildcard = true;
@ -397,21 +398,16 @@ impl Cors {
self self
} }
/// Allows users to make authenticated requests /// Allows users to make authenticated requests.
/// ///
/// If true, injects the `Access-Control-Allow-Credentials` header in responses. This allows /// If true, injects the `Access-Control-Allow-Credentials` header in responses. This allows
/// cookies and credentials to be submitted across domains as specified in /// cookies and credentials to be submitted across domains.
/// the [Fetch Standard CORS protocol].
/// ///
/// This option cannot be used in conjunction with an `allowed_origin` set to `All` and /// This option **CANNOT** be used in conjunction with option cannot be used in conjunction
/// `send_wildcards` set to `true`. /// with [wildcard origins](Self::send_wildcard()) configured. Doing so will result in an error
/// during server startup.
/// ///
/// Defaults to `false`. /// Defaults to disabled.
///
/// A server initialization error will occur if credentials are allowed, but the Origin is set
/// to send wildcards (`*`); this is not allowed by the CORS protocol.
///
/// [Fetch Standard CORS protocol]: https://fetch.spec.whatwg.org/#http-cors-protocol
pub fn supports_credentials(mut self) -> Cors { pub fn supports_credentials(mut self) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) { if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.supports_credentials = true; cors.supports_credentials = true;
@ -439,7 +435,7 @@ impl Cors {
self self
} }
/// Disable `Vary` header support. /// Disables `Vary` header support.
/// ///
/// When enabled the header `Vary: Origin` will be returned as per the Fetch Standard /// When enabled the header `Vary: Origin` will be returned as per the Fetch Standard
/// implementation guidelines. /// implementation guidelines.
@ -457,12 +453,12 @@ impl Cors {
self self
} }
/// Disable support for preflight requests. /// Disables preflight request handling.
/// ///
/// When enabled CORS middleware automatically handles `OPTIONS` requests. /// When enabled CORS middleware automatically handles `OPTIONS` requests. This is useful for
/// This is useful for application level middleware. /// application level middleware.
/// ///
/// By default *preflight* support is enabled. /// By default, preflight support is enabled.
pub fn disable_preflight(mut self) -> Cors { pub fn disable_preflight(mut self) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) { if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.preflight = false; cors.preflight = false;
@ -480,7 +476,7 @@ impl Cors {
/// and block requests based on pre-flight requests. Use this setting to allow cURL and other /// and block requests based on pre-flight requests. Use this setting to allow cURL and other
/// non-browser HTTP clients to function as normal, no matter what `Origin` the request has. /// non-browser HTTP clients to function as normal, no matter what `Origin` the request has.
/// ///
/// Defaults to `true`. /// Defaults to true.
pub fn block_on_origin_mismatch(mut self, block: bool) -> Cors { pub fn block_on_origin_mismatch(mut self, block: bool) -> Cors {
if let Some(cors) = cors(&mut self.inner, &self.error) { if let Some(cors) = cors(&mut self.inner, &self.error) {
cors.block_on_origin_mismatch = block; cors.block_on_origin_mismatch = block;

View File

@ -89,6 +89,9 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style, missing_docs)] #![deny(rust_2018_idioms, nonstandard_style, missing_docs)]
#![warn(future_incompatible)] #![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))]
pub mod config; pub mod config;
pub mod error; pub mod error;

View File

@ -49,6 +49,7 @@
#![warn(future_incompatible, missing_docs, missing_debug_implementations)] #![warn(future_incompatible, missing_docs, missing_debug_implementations)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use std::{borrow::Cow, fmt, sync::Arc, time::Duration}; use std::{borrow::Cow, fmt, sync::Arc, time::Duration};

View File

@ -3,6 +3,9 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)] #![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 std::{ use std::{
fmt, fmt,

View File

@ -3,6 +3,9 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)] #![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}; use derive_more::{Display, Error, From};
pub use redis_async::{error::Error as RespError, resp::RespValue, resp_array}; pub use redis_async::{error::Error as RespError, resp::RespValue, resp_array};

View File

@ -5,7 +5,7 @@ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Luca Palmieri <rust@lpalmieri.com>", "Luca Palmieri <rust@lpalmieri.com>",
] ]
description = "Session management for Actix We" description = "Session management for Actix Web"
keywords = ["http", "web", "framework", "async", "session"] keywords = ["http", "web", "framework", "async", "session"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git" repository = "https://github.com/actix/actix-extras.git"

View File

@ -2,6 +2,15 @@
## Unreleased ## Unreleased
## 0.7.1
- Fix doc examples.
## 0.7.0
- The `ApplySettings` trait now includes a type parameter, allowing multiple types to be implemented per configuration target.
- Implement `ApplySettings` for `ActixSettings`.
- `BasicSettings::from_default_template()` is now infallible.
- Rename `AtError => Error`. - Rename `AtError => Error`.
- Remove `AtResult` type alias. - Remove `AtResult` type alias.
- Update `toml` dependency to `0.8`. - Update `toml` dependency to `0.8`.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-settings" name = "actix-settings"
version = "0.6.0" version = "0.7.1"
authors = [ authors = [
"Joey Ezechiels <joey.ezechiels@gmail.com>", "Joey Ezechiels <joey.ezechiels@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",

View File

@ -3,9 +3,9 @@
> Easily manage Actix Web's settings from a TOML file and environment variables. > Easily manage Actix Web's settings from a TOML file and environment variables.
[![crates.io](https://img.shields.io/crates/v/actix-settings?label=latest)](https://crates.io/crates/actix-settings) [![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.6.0)](https://docs.rs/actix-settings/0.6.0) [![Documentation](https://docs.rs/actix-settings/badge.svg?version=0.7.1)](https://docs.rs/actix-settings/0.7.1)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-settings) ![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-settings)
[![Dependency Status](https://deps.rs/crate/actix-settings/0.6.0/status.svg)](https://deps.rs/crate/actix-settings/0.6.0) [![Dependency Status](https://deps.rs/crate/actix-settings/0.7.1/status.svg)](https://deps.rs/crate/actix-settings/0.7.1)
## Documentation & Resources ## Documentation & Resources

View File

@ -65,6 +65,7 @@
#![warn(future_incompatible, missing_docs, missing_debug_implementations)] #![warn(future_incompatible, missing_docs, missing_debug_implementations)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use std::{ use std::{
env, fmt, env, fmt,
@ -154,14 +155,11 @@ where
} }
/// Parse an instance of `Self` straight from the default TOML template. /// Parse an instance of `Self` straight from the default TOML template.
// TODO: make infallible pub fn from_default_template() -> Self {
// TODO: consider "template" rename Self::from_template(Self::DEFAULT_TOML_TEMPLATE).unwrap()
pub fn from_default_template() -> AsResult<Self> {
Self::from_template(Self::DEFAULT_TOML_TEMPLATE)
} }
/// Parse an instance of `Self` straight from the default TOML template. /// Parse an instance of `Self` straight from the default TOML template.
// TODO: consider "template" rename
pub fn from_template(template: &str) -> AsResult<Self> { pub fn from_template(template: &str) -> AsResult<Self> {
Ok(toml::from_str(template)?) Ok(toml::from_str(template)?)
} }
@ -196,7 +194,7 @@ where
/// use actix_settings::{Settings, Mode}; /// use actix_settings::{Settings, Mode};
/// ///
/// # fn inner() -> Result<(), actix_settings::Error> { /// # fn inner() -> Result<(), actix_settings::Error> {
/// let mut settings = Settings::from_default_template()?; /// let mut settings = Settings::from_default_template();
/// assert_eq!(settings.actix.mode, Mode::Development); /// assert_eq!(settings.actix.mode, Mode::Development);
/// ///
/// Settings::override_field(&mut settings.actix.mode, "production")?; /// Settings::override_field(&mut settings.actix.mode, "production")?;
@ -221,7 +219,7 @@ where
/// std::env::set_var("OVERRIDE__MODE", "production"); /// std::env::set_var("OVERRIDE__MODE", "production");
/// ///
/// # fn inner() -> Result<(), actix_settings::Error> { /// # fn inner() -> Result<(), actix_settings::Error> {
/// let mut settings = Settings::from_default_template()?; /// let mut settings = Settings::from_default_template();
/// assert_eq!(settings.actix.mode, Mode::Development); /// assert_eq!(settings.actix.mode, Mode::Development);
/// ///
/// Settings::override_field_with_env_var(&mut settings.actix.mode, "OVERRIDE__MODE")?; /// Settings::override_field_with_env_var(&mut settings.actix.mode, "OVERRIDE__MODE")?;
@ -242,17 +240,13 @@ where
} }
/// Extension trait for applying parsed settings to the server object. /// Extension trait for applying parsed settings to the server object.
pub trait ApplySettings { pub trait ApplySettings<S> {
/// Apply a [`BasicSettings`] value to `self`. /// Apply some settings object value to `self`.
///
/// [`BasicSettings`]: ./struct.BasicSettings.html
#[must_use] #[must_use]
fn apply_settings<A>(self, settings: &BasicSettings<A>) -> Self fn apply_settings(self, settings: &S) -> Self;
where
A: de::DeserializeOwned;
} }
impl<F, I, S, B> ApplySettings for HttpServer<F, I, S, B> impl<F, I, S, B> ApplySettings<ActixSettings> for HttpServer<F, I, S, B>
where where
F: Fn() -> I + Send + Clone + 'static, F: Fn() -> I + Send + Clone + 'static,
I: IntoServiceFactory<S, Request>, I: IntoServiceFactory<S, Request>,
@ -263,51 +257,48 @@ where
S::Future: 'static, S::Future: 'static,
B: MessageBody + 'static, B: MessageBody + 'static,
{ {
fn apply_settings<A>(mut self, settings: &BasicSettings<A>) -> Self fn apply_settings(mut self, settings: &ActixSettings) -> Self {
where if settings.tls.enabled {
A: de::DeserializeOwned,
{
if settings.actix.tls.enabled {
// for Address { host, port } in &settings.actix.hosts { // for Address { host, port } in &settings.actix.hosts {
// self = self.bind(format!("{}:{}", host, port)) // self = self.bind(format!("{}:{}", host, port))
// .unwrap(/*TODO*/); // .unwrap(/*TODO*/);
// } // }
todo!("[ApplySettings] TLS support has not been implemented yet."); unimplemented!("[ApplySettings] TLS support has not been implemented yet.");
} else { } else {
for Address { host, port } in &settings.actix.hosts { for Address { host, port } in &settings.hosts {
self = self.bind(format!("{host}:{port}")) self = self.bind(format!("{host}:{port}"))
.unwrap(/*TODO*/); .unwrap(/*TODO*/);
} }
} }
self = match settings.actix.num_workers { self = match settings.num_workers {
NumWorkers::Default => self, NumWorkers::Default => self,
NumWorkers::Manual(n) => self.workers(n), NumWorkers::Manual(n) => self.workers(n),
}; };
self = match settings.actix.backlog { self = match settings.backlog {
Backlog::Default => self, Backlog::Default => self,
Backlog::Manual(n) => self.backlog(n as u32), Backlog::Manual(n) => self.backlog(n as u32),
}; };
self = match settings.actix.max_connections { self = match settings.max_connections {
MaxConnections::Default => self, MaxConnections::Default => self,
MaxConnections::Manual(n) => self.max_connections(n), MaxConnections::Manual(n) => self.max_connections(n),
}; };
self = match settings.actix.max_connection_rate { self = match settings.max_connection_rate {
MaxConnectionRate::Default => self, MaxConnectionRate::Default => self,
MaxConnectionRate::Manual(n) => self.max_connection_rate(n), MaxConnectionRate::Manual(n) => self.max_connection_rate(n),
}; };
self = match settings.actix.keep_alive { self = match settings.keep_alive {
KeepAlive::Default => self, KeepAlive::Default => self,
KeepAlive::Disabled => self.keep_alive(ActixKeepAlive::Disabled), KeepAlive::Disabled => self.keep_alive(ActixKeepAlive::Disabled),
KeepAlive::Os => self.keep_alive(ActixKeepAlive::Os), KeepAlive::Os => self.keep_alive(ActixKeepAlive::Os),
KeepAlive::Seconds(n) => self.keep_alive(Duration::from_secs(n as u64)), KeepAlive::Seconds(n) => self.keep_alive(Duration::from_secs(n as u64)),
}; };
self = match settings.actix.client_timeout { self = match settings.client_timeout {
Timeout::Default => self, Timeout::Default => self,
Timeout::Milliseconds(n) => { Timeout::Milliseconds(n) => {
self.client_request_timeout(Duration::from_millis(n as u64)) self.client_request_timeout(Duration::from_millis(n as u64))
@ -315,7 +306,7 @@ where
Timeout::Seconds(n) => self.client_request_timeout(Duration::from_secs(n as u64)), Timeout::Seconds(n) => self.client_request_timeout(Duration::from_secs(n as u64)),
}; };
self = match settings.actix.client_shutdown { self = match settings.client_shutdown {
Timeout::Default => self, Timeout::Default => self,
Timeout::Milliseconds(n) => { Timeout::Milliseconds(n) => {
self.client_disconnect_timeout(Duration::from_millis(n as u64)) self.client_disconnect_timeout(Duration::from_millis(n as u64))
@ -323,7 +314,7 @@ where
Timeout::Seconds(n) => self.client_disconnect_timeout(Duration::from_secs(n as u64)), Timeout::Seconds(n) => self.client_disconnect_timeout(Duration::from_secs(n as u64)),
}; };
self = match settings.actix.shutdown_timeout { self = match settings.shutdown_timeout {
Timeout::Default => self, Timeout::Default => self,
Timeout::Milliseconds(_) => self.shutdown_timeout(1), Timeout::Milliseconds(_) => self.shutdown_timeout(1),
Timeout::Seconds(n) => self.shutdown_timeout(n as u64), Timeout::Seconds(n) => self.shutdown_timeout(n as u64),
@ -333,6 +324,23 @@ where
} }
} }
impl<F, I, S, B, A> ApplySettings<BasicSettings<A>> for HttpServer<F, I, S, B>
where
F: Fn() -> I + Send + Clone + 'static,
I: IntoServiceFactory<S, Request>,
S: ServiceFactory<Request, Config = AppConfig> + 'static,
S::Error: Into<WebError> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
S::Future: 'static,
B: MessageBody + 'static,
A: de::DeserializeOwned,
{
fn apply_settings(self, settings: &BasicSettings<A>) -> Self {
self.apply_settings(&settings.actix)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_web::App; use actix_web::App;
@ -347,7 +355,7 @@ mod tests {
#[test] #[test]
fn override_field_hosts() { fn override_field_hosts() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!( assert_eq!(
settings.actix.hosts, settings.actix.hosts,
@ -383,7 +391,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_hosts() { fn override_field_with_env_var_hosts() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!( assert_eq!(
settings.actix.hosts, settings.actix.hosts,
@ -421,7 +429,7 @@ mod tests {
#[test] #[test]
fn override_field_mode() { fn override_field_mode() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.mode, Mode::Development); assert_eq!(settings.actix.mode, Mode::Development);
Settings::override_field(&mut settings.actix.mode, "production").unwrap(); Settings::override_field(&mut settings.actix.mode, "production").unwrap();
assert_eq!(settings.actix.mode, Mode::Production); assert_eq!(settings.actix.mode, Mode::Production);
@ -429,7 +437,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_mode() { fn override_field_with_env_var_mode() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.mode, Mode::Development); assert_eq!(settings.actix.mode, Mode::Development);
std::env::set_var("OVERRIDE__MODE", "production"); std::env::set_var("OVERRIDE__MODE", "production");
Settings::override_field_with_env_var(&mut settings.actix.mode, "OVERRIDE__MODE").unwrap(); Settings::override_field_with_env_var(&mut settings.actix.mode, "OVERRIDE__MODE").unwrap();
@ -438,7 +446,7 @@ mod tests {
#[test] #[test]
fn override_field_enable_compression() { fn override_field_enable_compression() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert!(settings.actix.enable_compression); assert!(settings.actix.enable_compression);
Settings::override_field(&mut settings.actix.enable_compression, "false").unwrap(); Settings::override_field(&mut settings.actix.enable_compression, "false").unwrap();
assert!(!settings.actix.enable_compression); assert!(!settings.actix.enable_compression);
@ -446,7 +454,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_enable_compression() { fn override_field_with_env_var_enable_compression() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert!(settings.actix.enable_compression); assert!(settings.actix.enable_compression);
std::env::set_var("OVERRIDE__ENABLE_COMPRESSION", "false"); std::env::set_var("OVERRIDE__ENABLE_COMPRESSION", "false");
Settings::override_field_with_env_var( Settings::override_field_with_env_var(
@ -459,7 +467,7 @@ mod tests {
#[test] #[test]
fn override_field_enable_log() { fn override_field_enable_log() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert!(settings.actix.enable_log); assert!(settings.actix.enable_log);
Settings::override_field(&mut settings.actix.enable_log, "false").unwrap(); Settings::override_field(&mut settings.actix.enable_log, "false").unwrap();
assert!(!settings.actix.enable_log); assert!(!settings.actix.enable_log);
@ -467,7 +475,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_enable_log() { fn override_field_with_env_var_enable_log() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert!(settings.actix.enable_log); assert!(settings.actix.enable_log);
std::env::set_var("OVERRIDE__ENABLE_LOG", "false"); std::env::set_var("OVERRIDE__ENABLE_LOG", "false");
Settings::override_field_with_env_var( Settings::override_field_with_env_var(
@ -480,7 +488,7 @@ mod tests {
#[test] #[test]
fn override_field_num_workers() { fn override_field_num_workers() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.num_workers, NumWorkers::Default); assert_eq!(settings.actix.num_workers, NumWorkers::Default);
Settings::override_field(&mut settings.actix.num_workers, "42").unwrap(); Settings::override_field(&mut settings.actix.num_workers, "42").unwrap();
assert_eq!(settings.actix.num_workers, NumWorkers::Manual(42)); assert_eq!(settings.actix.num_workers, NumWorkers::Manual(42));
@ -488,7 +496,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_num_workers() { fn override_field_with_env_var_num_workers() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.num_workers, NumWorkers::Default); assert_eq!(settings.actix.num_workers, NumWorkers::Default);
std::env::set_var("OVERRIDE__NUM_WORKERS", "42"); std::env::set_var("OVERRIDE__NUM_WORKERS", "42");
Settings::override_field_with_env_var( Settings::override_field_with_env_var(
@ -501,7 +509,7 @@ mod tests {
#[test] #[test]
fn override_field_backlog() { fn override_field_backlog() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.backlog, Backlog::Default); assert_eq!(settings.actix.backlog, Backlog::Default);
Settings::override_field(&mut settings.actix.backlog, "42").unwrap(); Settings::override_field(&mut settings.actix.backlog, "42").unwrap();
assert_eq!(settings.actix.backlog, Backlog::Manual(42)); assert_eq!(settings.actix.backlog, Backlog::Manual(42));
@ -509,7 +517,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_backlog() { fn override_field_with_env_var_backlog() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.backlog, Backlog::Default); assert_eq!(settings.actix.backlog, Backlog::Default);
std::env::set_var("OVERRIDE__BACKLOG", "42"); std::env::set_var("OVERRIDE__BACKLOG", "42");
Settings::override_field_with_env_var(&mut settings.actix.backlog, "OVERRIDE__BACKLOG") Settings::override_field_with_env_var(&mut settings.actix.backlog, "OVERRIDE__BACKLOG")
@ -519,7 +527,7 @@ mod tests {
#[test] #[test]
fn override_field_max_connections() { fn override_field_max_connections() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.max_connections, MaxConnections::Default); assert_eq!(settings.actix.max_connections, MaxConnections::Default);
Settings::override_field(&mut settings.actix.max_connections, "42").unwrap(); Settings::override_field(&mut settings.actix.max_connections, "42").unwrap();
assert_eq!(settings.actix.max_connections, MaxConnections::Manual(42)); assert_eq!(settings.actix.max_connections, MaxConnections::Manual(42));
@ -527,7 +535,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_max_connections() { fn override_field_with_env_var_max_connections() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.max_connections, MaxConnections::Default); assert_eq!(settings.actix.max_connections, MaxConnections::Default);
std::env::set_var("OVERRIDE__MAX_CONNECTIONS", "42"); std::env::set_var("OVERRIDE__MAX_CONNECTIONS", "42");
Settings::override_field_with_env_var( Settings::override_field_with_env_var(
@ -540,7 +548,7 @@ mod tests {
#[test] #[test]
fn override_field_max_connection_rate() { fn override_field_max_connection_rate() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!( assert_eq!(
settings.actix.max_connection_rate, settings.actix.max_connection_rate,
MaxConnectionRate::Default MaxConnectionRate::Default
@ -554,7 +562,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_max_connection_rate() { fn override_field_with_env_var_max_connection_rate() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!( assert_eq!(
settings.actix.max_connection_rate, settings.actix.max_connection_rate,
MaxConnectionRate::Default MaxConnectionRate::Default
@ -573,7 +581,7 @@ mod tests {
#[test] #[test]
fn override_field_keep_alive() { fn override_field_keep_alive() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.keep_alive, KeepAlive::Default); assert_eq!(settings.actix.keep_alive, KeepAlive::Default);
Settings::override_field(&mut settings.actix.keep_alive, "42 seconds").unwrap(); Settings::override_field(&mut settings.actix.keep_alive, "42 seconds").unwrap();
assert_eq!(settings.actix.keep_alive, KeepAlive::Seconds(42)); assert_eq!(settings.actix.keep_alive, KeepAlive::Seconds(42));
@ -581,7 +589,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_keep_alive() { fn override_field_with_env_var_keep_alive() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.keep_alive, KeepAlive::Default); assert_eq!(settings.actix.keep_alive, KeepAlive::Default);
std::env::set_var("OVERRIDE__KEEP_ALIVE", "42 seconds"); std::env::set_var("OVERRIDE__KEEP_ALIVE", "42 seconds");
Settings::override_field_with_env_var( Settings::override_field_with_env_var(
@ -594,7 +602,7 @@ mod tests {
#[test] #[test]
fn override_field_client_timeout() { fn override_field_client_timeout() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.client_timeout, Timeout::Default); assert_eq!(settings.actix.client_timeout, Timeout::Default);
Settings::override_field(&mut settings.actix.client_timeout, "42 seconds").unwrap(); Settings::override_field(&mut settings.actix.client_timeout, "42 seconds").unwrap();
assert_eq!(settings.actix.client_timeout, Timeout::Seconds(42)); assert_eq!(settings.actix.client_timeout, Timeout::Seconds(42));
@ -602,7 +610,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_client_timeout() { fn override_field_with_env_var_client_timeout() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.client_timeout, Timeout::Default); assert_eq!(settings.actix.client_timeout, Timeout::Default);
std::env::set_var("OVERRIDE__CLIENT_TIMEOUT", "42 seconds"); std::env::set_var("OVERRIDE__CLIENT_TIMEOUT", "42 seconds");
Settings::override_field_with_env_var( Settings::override_field_with_env_var(
@ -615,7 +623,7 @@ mod tests {
#[test] #[test]
fn override_field_client_shutdown() { fn override_field_client_shutdown() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.client_shutdown, Timeout::Default); assert_eq!(settings.actix.client_shutdown, Timeout::Default);
Settings::override_field(&mut settings.actix.client_shutdown, "42 seconds").unwrap(); Settings::override_field(&mut settings.actix.client_shutdown, "42 seconds").unwrap();
assert_eq!(settings.actix.client_shutdown, Timeout::Seconds(42)); assert_eq!(settings.actix.client_shutdown, Timeout::Seconds(42));
@ -623,7 +631,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_client_shutdown() { fn override_field_with_env_var_client_shutdown() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.client_shutdown, Timeout::Default); assert_eq!(settings.actix.client_shutdown, Timeout::Default);
std::env::set_var("OVERRIDE__CLIENT_SHUTDOWN", "42 seconds"); std::env::set_var("OVERRIDE__CLIENT_SHUTDOWN", "42 seconds");
Settings::override_field_with_env_var( Settings::override_field_with_env_var(
@ -636,7 +644,7 @@ mod tests {
#[test] #[test]
fn override_field_shutdown_timeout() { fn override_field_shutdown_timeout() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.shutdown_timeout, Timeout::Default); assert_eq!(settings.actix.shutdown_timeout, Timeout::Default);
Settings::override_field(&mut settings.actix.shutdown_timeout, "42 seconds").unwrap(); Settings::override_field(&mut settings.actix.shutdown_timeout, "42 seconds").unwrap();
assert_eq!(settings.actix.shutdown_timeout, Timeout::Seconds(42)); assert_eq!(settings.actix.shutdown_timeout, Timeout::Seconds(42));
@ -644,7 +652,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_shutdown_timeout() { fn override_field_with_env_var_shutdown_timeout() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!(settings.actix.shutdown_timeout, Timeout::Default); assert_eq!(settings.actix.shutdown_timeout, Timeout::Default);
std::env::set_var("OVERRIDE__SHUTDOWN_TIMEOUT", "42 seconds"); std::env::set_var("OVERRIDE__SHUTDOWN_TIMEOUT", "42 seconds");
Settings::override_field_with_env_var( Settings::override_field_with_env_var(
@ -657,7 +665,7 @@ mod tests {
#[test] #[test]
fn override_field_tls_enabled() { fn override_field_tls_enabled() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert!(!settings.actix.tls.enabled); assert!(!settings.actix.tls.enabled);
Settings::override_field(&mut settings.actix.tls.enabled, "true").unwrap(); Settings::override_field(&mut settings.actix.tls.enabled, "true").unwrap();
assert!(settings.actix.tls.enabled); assert!(settings.actix.tls.enabled);
@ -665,7 +673,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_tls_enabled() { fn override_field_with_env_var_tls_enabled() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert!(!settings.actix.tls.enabled); assert!(!settings.actix.tls.enabled);
std::env::set_var("OVERRIDE__TLS_ENABLED", "true"); std::env::set_var("OVERRIDE__TLS_ENABLED", "true");
Settings::override_field_with_env_var( Settings::override_field_with_env_var(
@ -678,7 +686,7 @@ mod tests {
#[test] #[test]
fn override_field_tls_certificate() { fn override_field_tls_certificate() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!( assert_eq!(
settings.actix.tls.certificate, settings.actix.tls.certificate,
Path::new("path/to/cert/cert.pem") Path::new("path/to/cert/cert.pem")
@ -696,7 +704,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_tls_certificate() { fn override_field_with_env_var_tls_certificate() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!( assert_eq!(
settings.actix.tls.certificate, settings.actix.tls.certificate,
Path::new("path/to/cert/cert.pem") Path::new("path/to/cert/cert.pem")
@ -718,7 +726,7 @@ mod tests {
#[test] #[test]
fn override_field_tls_private_key() { fn override_field_tls_private_key() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!( assert_eq!(
settings.actix.tls.private_key, settings.actix.tls.private_key,
Path::new("path/to/cert/key.pem") Path::new("path/to/cert/key.pem")
@ -736,7 +744,7 @@ mod tests {
#[test] #[test]
fn override_field_with_env_var_tls_private_key() { fn override_field_with_env_var_tls_private_key() {
let mut settings = Settings::from_default_template().unwrap(); let mut settings = Settings::from_default_template();
assert_eq!( assert_eq!(
settings.actix.tls.private_key, settings.actix.tls.private_key,
Path::new("path/to/cert/key.pem") Path::new("path/to/cert/key.pem")

View File

@ -26,7 +26,7 @@ pub struct ActixSettings {
/// Marker of intended deployment environment. /// Marker of intended deployment environment.
pub mode: Mode, pub mode: Mode,
/// True if the [`Compress`](actix_web::middleware::Compress) middleware should be enabled. /// True if the `Compress` middleware should be enabled.
pub enable_compression: bool, pub enable_compression: bool,
/// True if the [`Logger`](actix_web::middleware::Logger) middleware should be enabled. /// True if the [`Logger`](actix_web::middleware::Logger) middleware should be enabled.

View File

@ -1,15 +1,15 @@
[package] [package]
name = "actix-web-httpauth" name = "actix-web-httpauth"
version = "0.8.1" version = "0.8.1"
description = "HTTP authentication schemes for Actix Web"
categories = ["web-programming"]
keywords = ["http", "web", "framework", "authentication", "security"]
authors = [ authors = [
"svartalf <self@svartalf.info>", "svartalf <self@svartalf.info>",
"Yuki Okushi <huyuumi.dev@gmail.com>", "Yuki Okushi <huyuumi.dev@gmail.com>",
] ]
description = "HTTP authentication schemes for Actix Web"
keywords = ["http", "web", "framework", "authentication", "security"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-extras.git" repository = "https://github.com/actix/actix-extras"
categories = ["web-programming::http-server"]
license.workspace = true license.workspace = true
edition.workspace = true edition.workspace = true
rust-version.workspace = true rust-version.workspace = true

View File

@ -2,7 +2,7 @@ use super::AuthenticationError;
use crate::headers::www_authenticate::Challenge; use crate::headers::www_authenticate::Challenge;
/// Trait implemented for types that provides configuration for the authentication /// Trait implemented for types that provides configuration for the authentication
/// [extractors](super::AuthExtractor). /// [extractors](crate::extractors).
pub trait AuthExtractorConfig { pub trait AuthExtractorConfig {
/// Associated challenge type. /// Associated challenge type.
type Inner: Challenge; type Inner: Challenge;

View File

@ -9,9 +9,10 @@ use crate::headers::authorization::{errors::ParseError, scheme::Scheme};
/// Credentials for `Bearer` authentication scheme, defined in [RFC 6750]. /// Credentials for `Bearer` authentication scheme, defined in [RFC 6750].
/// ///
/// Should be used in combination with [`Authorization`](super::Authorization) header. /// Should be used in combination with [`Authorization`] header.
/// ///
/// [RFC 6750]: https://tools.ietf.org/html/rfc6750 /// [RFC 6750]: https://tools.ietf.org/html/rfc6750
/// [`Authorization`]: crate::headers::authorization::Authorization
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] #[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
pub struct Bearer { pub struct Bearer {
token: Cow<'static, str>, token: Cow<'static, str>,

View File

@ -17,6 +17,9 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs)] #![warn(future_incompatible, 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 mod extractors; pub mod extractors;
pub mod headers; pub mod headers;

10
actix-ws/CHANGELOG.md Normal file
View File

@ -0,0 +1,10 @@
# Changelog
## Unreleased
- 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.
## 0.2.5
- Adopted into @actix org from <https://git.asonix.dog/asonix/actix-actorless-websockets>.

31
actix-ws/Cargo.toml Normal file
View File

@ -0,0 +1,31 @@
[package]
name = "actix-ws"
version = "0.2.0"
description = "WebSockets for Actix Web, without actors"
categories = ["web-programming::websocket"]
keywords = ["actix", "web", "websocket", "websockets", "http"]
authors = [
"asonix <asonix@asonix.dog>",
"Rob Ede <robjtede@icloud.com>",
]
repository = "https://github.com/actix/actix-extras"
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
actix-codec = "0.5"
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"] }
[dev-dependencies]
actix-rt = "2.6"
actix-web = "4.0.1"
anyhow = "1.0"
futures = "0.3"
log = "0.4"
pretty_env_logger = "0.5"
tokio = { version = "1", features = ["sync"] }

1
actix-ws/LICENSE-APACHE Symbolic link
View File

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

1
actix-ws/LICENSE-MIT Symbolic link
View File

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

74
actix-ws/README.md Normal file
View File

@ -0,0 +1,74 @@
# Actix WS (Next Gen)
> WebSockets for Actix Web, without actors.
[![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 & Resources
- [API Documentation](https://docs.rs/actix-ws)
- [Example Projects](https://github.com/actix/examples/tree/master/websockets)
- Minimum Supported Rust Version (MSRV): 1.68
## Usage
```toml
# Cargo.toml
anyhow = "1"
actix-web = "4"
actix-ws-ng = "0.3"
```
```rust
// main.rs
use actix_web::{middleware::Logger, web, App, Error, HttpRequest, HttpResponse, HttpServer};
use actix_ws::Message;
async fn ws(req: HttpRequest, body: web::Payload) -> Result<HttpResponse, Error> {
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 {
match msg {
Message::Ping(bytes) => {
if session.pong(&bytes).await.is_err() {
return;
}
}
Message::Text(s) => println!("Got text, {}", s),
_ => break,
}
}
let _ = session.close(None).await;
});
Ok(response)
}
#[actix_web::main]
async fn main() -> Result<(), anyhow::Error> {
HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.route("/ws", web::get().to(ws))
})
.bind("127.0.0.1:8080")?
.run()
.await?;
Ok(())
}
```
## License
This project is licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.

208
actix-ws/examples/chat.rs Normal file
View File

@ -0,0 +1,208 @@
use std::{
sync::Arc,
time::{Duration, Instant},
};
use actix_web::{middleware::Logger, web, App, HttpRequest, HttpResponse, HttpServer};
use actix_ws::{Message, Session};
use futures::stream::{FuturesUnordered, StreamExt as _};
use log::info;
use tokio::sync::Mutex;
#[derive(Clone)]
struct Chat {
inner: Arc<Mutex<ChatInner>>,
}
struct ChatInner {
sessions: Vec<Session>,
}
impl Chat {
fn new() -> Self {
Chat {
inner: Arc::new(Mutex::new(ChatInner {
sessions: Vec::new(),
})),
}
}
async fn insert(&self, session: Session) {
self.inner.lock().await.sessions.push(session);
}
async fn send(&self, msg: String) {
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"))
});
}
while let Some(res) = unordered.next().await {
if let Ok(session) = res {
inner.sessions.push(session);
}
}
}
}
async fn ws(
req: HttpRequest,
body: web::Payload,
chat: web::Data<Chat>,
) -> Result<HttpResponse, actix_web::Error> {
let (response, mut session, mut stream) = actix_ws::handle(&req, body)?;
chat.insert(session.clone()).await;
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));
loop {
interval.tick().await;
if session2.ping(b"").await.is_err() {
break;
}
if Instant::now().duration_since(*alive2.lock().await) > Duration::from_secs(10) {
let _ = session2.close(None).await;
break;
}
}
});
actix_rt::spawn(async move {
while let Some(Ok(msg)) = stream.next().await {
match msg {
Message::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;
}
Message::Close(reason) => {
let _ = session.close(reason).await;
info!("Got close, bailing");
return;
}
Message::Continuation(_) => {
let _ = session.close(None).await;
info!("Got continuation, bailing");
return;
}
Message::Pong(_) => {
*alive.lock().await = Instant::now();
}
_ => (),
};
}
let _ = session.close(None).await;
});
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;
}
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>
"#;
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 || {
App::new()
.wrap(Logger::default())
.app_data(web::Data::new(chat.clone()))
.route("/", web::get().to(index))
.route("/ws", web::get().to(ws))
})
.bind("127.0.0.1:8080")?
.run()
.await?;
Ok(())
}

183
actix-ws/src/fut.rs Normal file
View File

@ -0,0 +1,183 @@
use std::{
collections::VecDeque,
future::poll_fn,
io,
pin::Pin,
task::{Context, Poll},
};
use actix_codec::{Decoder, Encoder};
use actix_http::{
ws::{Codec, Frame, Message, ProtocolError},
Payload,
};
use actix_web::{
web::{Bytes, BytesMut},
Error,
};
use futures_core::stream::Stream;
use tokio::sync::mpsc::Receiver;
/// A response body for Websocket HTTP Requests
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,
closing: bool,
}
impl StreamingBody {
pub(super) fn new(session_rx: Receiver<Message>) -> Self {
StreamingBody {
session_rx,
messages: VecDeque::new(),
buf: BytesMut::new(),
codec: Codec::new(),
closing: false,
}
}
}
impl MessageStream {
pub(super) fn new(payload: Payload) -> Self {
MessageStream {
payload,
messages: VecDeque::new(),
buf: BytesMut::new(),
codec: Codec::new(),
closing: false,
}
}
/// Wait for the next item from the message stream
///
/// ```rust,ignore
/// while let Some(Ok(msg)) = stream.recv().await {
/// // handle message
/// }
/// ```
pub async fn recv(&mut self) -> Option<Result<Message, ProtocolError>> {
poll_fn(|cx| Pin::new(&mut *self).poll_next(cx)).await
}
}
impl Stream for StreamingBody {
type Item = Result<Bytes, Error>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
if this.closing {
return Poll::Ready(None);
}
loop {
match Pin::new(&mut this.session_rx).poll_recv(cx) {
Poll::Ready(Some(msg)) => {
this.messages.push_back(msg);
}
Poll::Ready(None) => {
this.closing = true;
break;
}
Poll::Pending => break,
}
}
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 !this.buf.is_empty() {
return Poll::Ready(Some(Ok(this.buf.split().freeze())));
}
Poll::Pending
}
}
impl Stream for MessageStream {
type Item = Result<Message, ProtocolError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
// Return the first message in the queue if one exists
//
// This is faster than polling and parsing
if let Some(msg) = this.messages.pop_front() {
return Poll::Ready(Some(Ok(msg)));
}
if !this.closing {
// Read in bytes until there's nothing left to read
loop {
match Pin::new(&mut this.payload).poll_next(cx) {
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(None) => {
this.closing = true;
break;
}
Poll::Pending => break,
}
}
}
// Create messages until there's no more bytes left
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()))
})?
.to_string();
Message::Text(s.into())
}
Frame::Binary(bytes) => Message::Binary(bytes),
Frame::Ping(bytes) => Message::Ping(bytes),
Frame::Pong(bytes) => Message::Pong(bytes),
Frame::Close(reason) => Message::Close(reason),
Frame::Continuation(item) => Message::Continuation(item),
};
this.messages.push_back(message);
}
// Return the first message in the queue
if let Some(msg) = this.messages.pop_front() {
return Poll::Ready(Some(Ok(msg)));
}
// If we've exhausted our message queue and we're closing, close the stream
if this.closing {
return Poll::Ready(None);
}
Poll::Pending
}
}

84
actix-ws/src/lib.rs Normal file
View File

@ -0,0 +1,84 @@
//! WebSockets for Actix Web, without actors.
//!
//! 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};
use actix_http::{
body::{BodyStream, MessageBody},
ws::handshake,
};
use actix_web::{web, HttpRequest, HttpResponse};
use tokio::sync::mpsc::channel;
mod fut;
mod session;
pub use self::{
fut::{MessageStream, StreamingBody},
session::{Closed, Session},
};
/// Begin handling websocket traffic
///
/// ```no_run
/// use actix_web::{middleware::Logger, web, App, Error, HttpRequest, HttpResponse, HttpServer};
/// use actix_ws::Message;
/// use futures::stream::StreamExt as _;
///
/// async fn ws(req: HttpRequest, body: web::Payload) -> Result<HttpResponse, Error> {
/// 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 {
/// match msg {
/// Message::Ping(bytes) => {
/// if session.pong(&bytes).await.is_err() {
/// return;
/// }
/// }
/// Message::Text(s) => println!("Got text, {}", s),
/// _ => break,
/// }
/// }
///
/// let _ = session.close(None).await;
/// });
///
/// Ok(response)
/// }
///
/// #[actix_rt::main]
/// async fn main() -> Result<(), anyhow::Error> {
/// HttpServer::new(move || {
/// App::new()
/// .wrap(Logger::default())
/// .route("/ws", web::get().to(ws))
/// })
/// .bind("127.0.0.1:8080")?
/// .run()
/// .await?;
///
/// Ok(())
/// }
/// ```
pub fn handle(
req: &HttpRequest,
body: web::Payload,
) -> Result<(HttpResponse, Session, MessageStream), actix_web::Error> {
let mut response = handshake(req.head())?;
let (tx, rx) = channel(32);
Ok((
response
.message_body(BodyStream::new(StreamingBody::new(rx)).boxed())?
.into(),
Session::new(tx),
MessageStream::new(body.into_inner()),
))
}

143
actix-ws/src/session.rs Normal file
View File

@ -0,0 +1,143 @@
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use actix_http::ws::{CloseReason, 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.
#[derive(Clone)]
pub struct Session {
inner: Option<Sender<Message>>,
closed: Arc<AtomicBool>,
}
/// The error representing a closed websocket 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 std::error::Error for Closed {}
impl Session {
pub(super) fn new(inner: Sender<Message>) -> Self {
Session {
inner: Some(inner),
closed: Arc::new(AtomicBool::new(false)),
}
}
fn pre_check(&mut self) {
if self.closed.load(Ordering::Relaxed) {
self.inner.take();
}
}
/// Send text into the websocket
///
/// ```rust,ignore
/// 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();
if let Some(inner) = self.inner.as_mut() {
inner
.send(Message::Text(msg.into()))
.await
.map_err(|_| Closed)
} else {
Err(Closed)
}
}
/// Send raw bytes into the websocket
///
/// ```rust,ignore
/// 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();
if let Some(inner) = self.inner.as_mut() {
inner
.send(Message::Binary(msg.into()))
.await
.map_err(|_| Closed)
} else {
Err(Closed)
}
}
/// Ping the client
///
/// For many applications, it will be important to send regular pings to keep track of if the
/// client has disconnected
///
/// ```rust,ignore
/// if session.ping(b"").await.is_err() {
/// // session is closed
/// }
/// ```
pub async fn ping(&mut self, msg: &[u8]) -> Result<(), Closed> {
self.pre_check();
if let Some(inner) = self.inner.as_mut() {
inner
.send(Message::Ping(Bytes::copy_from_slice(msg)))
.await
.map_err(|_| Closed)
} else {
Err(Closed)
}
}
/// Pong the client
///
/// ```rust,ignore
/// 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() {
inner
.send(Message::Pong(Bytes::copy_from_slice(msg)))
.await
.map_err(|_| Closed)
} else {
Err(Closed)
}
}
/// Send a close message, and consume the session
///
/// All clones will return `Err(Closed)` if used after this call
///
/// ```rust,ignore
/// 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)
} else {
Err(Closed)
}
}
}

37
justfile Normal file
View File

@ -0,0 +1,37 @@
_list:
@just --list
# Document crates in workspace.
doc:
RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --all-features
# Document crates in workspace and watch for changes.
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
# Check for unintentional external type exposure on all crates in workspace.
check-external-types-all toolchain="+nightly":
#!/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
echo
echo
done
exit $exit
# Check for unintentional external type exposure on all crates in workspace.
check-external-types-all-table toolchain="+nightly":
#!/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
done
# Check for unintentional external type exposure on a crate.
check-external-types-manifest manifest_path toolchain="+nightly" *extra_args="":
cargo {{toolchain}} check-external-types --manifest-path "{{manifest_path}}" {{extra_args}}