diff --git a/CHANGELOG.md b/CHANGELOG.md index b4a857a7..dbf5e511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Built-in WebSocket support behind rkt's optional `ws` feature, available as + `rkt::ws`. The `rkt_ws` crate remains a deprecated compatibility shim for one + full minor release cycle. - Support for the [PROXY protocol] (v1 and v2) behind the new `proxy-proto` crate feature. Setting the `proxy_protocol` configuration option (env `ROCKET_PROXY_PROTOCOL`) to `true` requires every connection to begin with a diff --git a/Cargo.toml b/Cargo.toml index c85589c3..23aeb030 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ ] [workspace.package] -version = "1.2.0" +version = "1.3.0" authors = ["Sergio Benitez "] license = "MIT OR Apache-2.0" edition = "2021" @@ -20,9 +20,9 @@ repository = "https://github.com/rustfoo/rkt" readme = "README.md" [workspace.dependencies] -rkt = { version = "1.2.0", path = "core/lib/", default-features = false } -rkt_http = { version = "1.2.0", path = "core/http/" } -rkt_codegen = { version = "1.2.0", path = "core/codegen/" } +rkt = { version = "1.3.0", path = "core/lib/", default-features = false } +rkt_http = { version = "1.3.0", path = "core/http/" } +rkt_codegen = { version = "1.3.0", path = "core/codegen/" } [workspace.lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(nightly)'] } diff --git a/README.md b/README.md index 76597bf3..4b1ba632 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Add `rkt` to your `Cargo.toml`: ```toml [dependencies] -rkt = "1.2.0" +rkt = "1.3.0" ``` ## Crates @@ -45,7 +45,6 @@ rkt = "1.2.0" |-------|-------------|------| | `rkt` | Core framework | [![docs.rs](https://img.shields.io/docsrs/rkt)](https://docs.rs/rkt) | | `rkt_dyn_templates` | Dynamic template rendering (Tera, Handlebars, MiniJinja) | [![docs.rs](https://img.shields.io/docsrs/rkt_dyn_templates)](https://docs.rs/rkt_dyn_templates) | -| `rkt_ws` | WebSocket support | [![docs.rs](https://img.shields.io/docsrs/rkt_ws)](https://docs.rs/rkt_ws) | ## Features @@ -53,7 +52,7 @@ rkt = "1.2.0" - **HTTP/3 preview** — via s2n-quic (enable with `http3-preview`) - **TLS & mTLS** — via Rustls (enable with `tls` / `mtls`) - **Secret cookies** — signed and encrypted cookie support (enable with `secrets`) -- **WebSockets** — first-class support via `rkt_ws` +- **WebSockets** — first-class support via `rkt::ws` (enable with `ws`) - **Dynamic templates** — Tera, Handlebars, and MiniJinja via `rkt_dyn_templates` - **Tracing** — structured logging via the `tracing` ecosystem - **Type-safe routing** — compile-time checked routes, guards, and responders diff --git a/contrib/dyn_templates/README.md b/contrib/dyn_templates/README.md index e01a16bd..18980fa1 100644 --- a/contrib/dyn_templates/README.md +++ b/contrib/dyn_templates/README.md @@ -22,7 +22,7 @@ and automatically reloads templates when compiled in debug mode. It supports [Ha ```toml [dependencies.rkt_dyn_templates] - version = "1.2.0" + version = "1.3.0" features = ["handlebars", "tera", "minijinja"] ``` diff --git a/contrib/ws/Cargo.toml b/contrib/ws/Cargo.toml index ae21410a..0e38eef8 100644 --- a/contrib/ws/Cargo.toml +++ b/contrib/ws/Cargo.toml @@ -2,7 +2,7 @@ name = "rkt_ws" version.workspace = true authors.workspace = true -description = "WebSocket support for rkt." +description = "Deprecated compatibility shim for rkt WebSocket support." documentation = "https://docs.rs/rkt_ws/latest/rkt_ws/" homepage.workspace = true repository.workspace = true @@ -17,14 +17,10 @@ workspace = true [features] default = ["tungstenite"] -tungstenite = ["tokio-tungstenite"] +tungstenite = [] [dependencies] -tokio-tungstenite = { version = "0.30", optional = true } - -[dependencies.rkt] -workspace = true -default-features = false +rkt = { workspace = true, default-features = false, features = ["ws"] } [package.metadata.docs.rs] all-features = true diff --git a/contrib/ws/README.md b/contrib/ws/README.md index e5abaac5..e8d617bc 100644 --- a/contrib/ws/README.md +++ b/contrib/ws/README.md @@ -1,35 +1,18 @@ -# `ws` [![ci.svg]][ci] [![crates.io]][crate] [![docs.svg]][crate docs] +# `rkt_ws` (deprecated) -[crates.io]: https://img.shields.io/crates/v/rkt_ws.svg -[crate]: https://crates.io/crates/rkt_ws -[docs.svg]: https://img.shields.io/badge/web-master-red.svg?style=flat&label=docs&colorB=d33847 -[crate docs]: https://docs.rs/rkt_ws/latest/rkt_ws/ -[ci.svg]: https://github.com/rustfoo/rkt/workflows/CI/badge.svg -[ci]: https://github.com/rustfoo/rkt/actions +WebSocket support now lives in [`rkt::ws`](https://docs.rs/rkt/latest/rkt/ws/). -This crate provides WebSocket support for rkt via integration with rkt's -[connection upgrades] API. +```toml +[dependencies] +rkt = { version = "1.3.0", features = ["ws"] } +``` -# Usage +Replace `rkt_ws` or `ws` imports with `rkt::ws`: - 1. Depend on `rkt_ws`, renamed here to `ws`: +```rust +use rkt::ws::{WebSocket, Stream}; +``` - ```toml - [dependencies] - ws = { package = "rkt_ws", version = "1.2.0" } - ``` - - 2. Use it! - - ```rust - #[get("/echo")] - fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { - ws::Stream! { ws => - for await message in ws { - yield message?; - } - } - } - ``` - -See the [crate docs] for full details. +`rkt_ws` remains a source-compatible compatibility shim for one full minor +release cycle and will be removed in the following minor release. Its legacy +`tungstenite` feature remains a no-op compatibility alias. diff --git a/contrib/ws/src/lib.rs b/contrib/ws/src/lib.rs index 6e218e40..85c10021 100644 --- a/contrib/ws/src/lib.rs +++ b/contrib/ws/src/lib.rs @@ -1,320 +1,59 @@ -//! WebSocket support for Rkt. +//! Deprecated compatibility shim for [`rkt::ws`]. //! -//! This crate implements support for WebSockets via Rkt's [connection -//! upgrade API](rkt::Response#upgrading) and -//! [tungstenite](tokio_tungstenite). -//! -//! # Usage -//! -//! Depend on the crate. Here, we rename the dependency to `ws` for convenience: -//! -//! ```toml -//! [dependencies] -//! ws = { package = "rkt_ws", version = "0.1.3" } -//! ``` -//! -//! Then, use [`WebSocket`] as a request guard in any route and either call -//! [`WebSocket::channel()`] or return a stream via [`Stream!`] or -//! [`WebSocket::stream()`] in the handler. The examples below are equivalent: -//! -//! ```rust -//! # use rkt::get; -//! # use rkt_ws as ws; -//! # -//! #[get("/echo?channel")] -//! fn echo_channel(ws: ws::WebSocket) -> ws::Channel<'static> { -//! use rkt::futures::{SinkExt, StreamExt}; -//! -//! ws.channel(move |mut stream| Box::pin(async move { -//! while let Some(message) = stream.next().await { -//! let _ = stream.send(message?).await; -//! } -//! -//! Ok(()) -//! })) -//! } -//! -//! #[get("/echo?stream")] -//! fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { -//! ws::Stream! { ws => -//! for await message in ws { -//! yield message?; -//! } -//! } -//! } -//! -//! #[get("/echo?compose")] -//! fn echo_compose(ws: ws::WebSocket) -> ws::Stream!['static] { -//! ws.stream(|io| io) -//! } -//! ``` -//! -//! WebSocket connections are configurable via [`WebSocket::config()`]: -//! -//! ```rust -//! # use rkt::get; -//! # use rkt_ws as ws; -//! # -//! #[get("/echo")] -//! fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { -//! let ws = ws.config(ws::Config::default().max_message_size(Some(8192))); -//! -//! ws::Stream! { ws => -//! for await message in ws { -//! yield message?; -//! } -//! } -//! } -//! ``` +//! New applications should enable rkt's `ws` feature and import WebSocket +//! types from `rkt::ws`. Replace `rkt_ws` (or a `ws` dependency alias) imports +//! with `rkt::ws`. This crate will be removed after one full minor release +//! cycle. +#![deprecated(note = "rkt_ws is deprecated; enable rkt's `ws` feature and use rkt::ws instead")] #![doc(html_root_url = "https://docs.rs/rkt_ws/latest/rkt_ws")] -#![doc(html_favicon_url = "https://rkt.rs/images/favicon.ico")] -#![doc(html_logo_url = "https://rkt.rs/images/logo-boxed.png")] -mod tungstenite { - #[doc(inline)] - pub use tokio_tungstenite::tungstenite::*; -} +#[deprecated(since = "1.3.0", note = "use `rkt::ws::WebSocket`")] +pub type WebSocket = rkt::ws::WebSocket; -mod duplex; -mod websocket; +#[deprecated(since = "1.3.0", note = "use `rkt::ws::Channel`")] +pub type Channel<'r> = rkt::ws::Channel<'r>; -pub use self::websocket::{Channel, WebSocket}; +#[deprecated(since = "1.3.0", note = "use `rkt::ws::Config`")] +pub type Config = rkt::ws::Config; -/// A WebSocket message. -/// -/// A value of this type is typically constructed by calling `.into()` on a -/// supported message type. This includes strings via `&str` and `String` and -/// bytes via `&[u8]` and `Vec`: -/// -/// ```rust -/// # use rkt::get; -/// # use rkt_ws as ws; -/// # -/// #[get("/echo")] -/// fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { -/// ws::Stream! { ws => -/// yield "Hello".into(); -/// yield String::from("Hello").into(); -/// yield (&[1u8, 2, 3][..]).into(); -/// yield vec![1u8, 2, 3].into(); -/// } -/// } -/// ``` -/// -/// Other kinds of messages can be constructed directly: -/// -/// ```rust -/// # use rkt::get; -/// # use rkt_ws as ws; -/// # -/// #[get("/echo")] -/// fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { -/// ws::Stream! { ws => -/// yield ws::Message::Ping(vec![b'h', b'i'].into()) -/// } -/// } -/// ``` -pub use self::tungstenite::Message; +#[deprecated(since = "1.3.0", note = "use `rkt::ws::Message`")] +pub type Message = rkt::ws::Message; -/// WebSocket connection configuration. -/// -/// The default configuration for a [`WebSocket`] can be changed by calling -/// [`WebSocket::config()`] with a value of this type. The defaults are obtained -/// via [`Default::default()`]. You don't generally need to reconfigure a -/// `WebSocket` unless you're certain you need different values. In other words, -/// this structure should rarely be used. -/// -/// # Example -/// -/// ```rust -/// # use rkt::get; -/// # use rkt_ws as ws; -/// use rkt::data::ToByteUnit; -/// -/// #[get("/echo")] -/// fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { -/// let ws = ws.config(ws::Config::default() -/// // Decrease the maximum (complete) message size to 4MiB. -/// .max_message_size(Some(4.mebibytes().as_u64() as usize)) -/// // Decrease the maximum size of _one_ frame (not message) to 1MiB. -/// .max_frame_size(Some(1.mebibytes().as_u64() as usize))); -/// -/// ws::Stream! { ws => -/// for await message in ws { -/// yield message?; -/// } -/// } -/// } -/// ``` -/// -/// **Original `tungstenite` Documentation Follows** -/// -pub use self::tungstenite::protocol::WebSocketConfig as Config; +/// Types representing incoming and/or outgoing `async` [`Message`] streams. +pub mod stream { + #[deprecated(since = "1.3.0", note = "use `rkt::ws::stream::DuplexStream`")] + pub type DuplexStream = rkt::ws::stream::DuplexStream; + + #[deprecated(since = "1.3.0", note = "use `rkt::ws::stream::MessageStream`")] + pub type MessageStream<'r, S> = rkt::ws::stream::MessageStream<'r, S>; +} /// Structures for constructing raw WebSocket frames. pub mod frame { - pub use crate::tungstenite::protocol::frame::coding::CloseCode; - pub use crate::tungstenite::protocol::frame::{CloseFrame, Frame}; - #[doc(hidden)] - pub use crate::Message; -} + #[deprecated(since = "1.3.0", note = "use `rkt::ws::frame::CloseCode`")] + pub type CloseCode = rkt::ws::frame::CloseCode; -/// Types representing incoming and/or outgoing `async` [`Message`] streams. -pub mod stream { - pub use crate::duplex::DuplexStream; - pub use crate::websocket::MessageStream; + #[deprecated(since = "1.3.0", note = "use `rkt::ws::frame::CloseFrame`")] + pub type CloseFrame = rkt::ws::frame::CloseFrame; + + #[deprecated(since = "1.3.0", note = "use `rkt::ws::frame::Frame`")] + pub type Frame = rkt::ws::frame::Frame; + + #[doc(hidden)] + pub use rkt::ws::frame::Message; } -/// Library [`Error`](crate::result::Error) and -/// [`Result`](crate::result::Result) types. +/// Library [`Error`](result::Error) and [`Result`](result::Result) types. pub mod result { - pub use crate::tungstenite::error::{Error, Result}; -} + #[deprecated(since = "1.3.0", note = "use `rkt::ws::result::Error`")] + pub type Error = rkt::ws::result::Error; -/// Type and expression macro for `async` WebSocket [`Message`] streams. -/// -/// This macro can be used both where types are expected or -/// where expressions are expected. -/// -/// # Type Position -/// -/// When used in a type position, the macro invoked as `Stream['r]` expands to: -/// -/// - [`MessageStream`]`<'r, impl `[`Stream`]`>> + 'r>` -/// -/// The lifetime need not be specified as `'r`. For instance, `Stream['request]` -/// is valid and expands as expected: -/// -/// - [`MessageStream`]`<'request, impl `[`Stream`]`>> + 'request>` -/// -/// As a convenience, when the macro is invoked as `Stream![]`, the lifetime -/// defaults to `'static`. That is, `Stream![]` is equivalent to -/// `Stream!['static]`. -/// -/// [`MessageStream`]: crate::stream::MessageStream -/// [`Stream`]: rkt::futures::stream::Stream -/// [`Result`]: crate::result::Result -/// [`Message`]: crate::Message -/// -/// # Expression Position -/// -/// When invoked as an expression, the macro behaves similarly to rkt's -/// [`stream!`](rkt::response::stream::stream) macro. Specifically, it -/// supports `yield` and `for await` syntax. It is invoked as follows: -/// -/// ```rust -/// # use rkt::get; -/// use rkt_ws as ws; -/// -/// #[get("/")] -/// fn echo(ws: ws::WebSocket) -> ws::Stream![] { -/// ws::Stream! { ws => -/// for await message in ws { -/// yield message?; -/// yield "foo".into(); -/// yield vec![1, 2, 3, 4].into(); -/// } -/// } -/// } -/// ``` -/// -/// It enjoins the following type requirements: -/// -/// * The type of `ws` _must_ be [`WebSocket`]. `ws` can be any ident. -/// * The type of yielded expressions (`expr` in `yield expr`) _must_ be [`Message`]. -/// * The `Err` type of expressions short-circuited with `?` _must_ be [`Error`]. -/// -/// [`Error`]: crate::result::Error -/// -/// The macro takes any series of statements and expands them into an expression -/// of type `impl Stream>`, a stream that `yield`s elements of -/// type [`Result`]``. It automatically converts yielded items of type `T` into -/// `Ok(T)`. It supports any Rust statement syntax with the following -/// extensions: -/// -/// * `?` short-circuits stream termination on `Err` -/// -/// The type of the error value must be [`Error`]. -///

-/// -/// * `yield expr` -/// -/// Yields the result of evaluating `expr` to the caller (the stream -/// consumer) wrapped in `Ok`. -/// -/// `expr` must be of type `T`. -///

-/// -/// * `for await x in stream { .. }` -/// -/// `await`s the next element in `stream`, binds it to `x`, and executes the -/// block with the binding. -/// -/// `stream` must implement `Stream`; the type of `x` is `T`. -/// -/// ### Examples -/// -/// Borrow from the request. Send a single message and close: -/// -/// ```rust -/// # use rkt::get; -/// use rkt_ws as ws; -/// -/// #[get("/hello/")] -/// fn ws_hello(ws: ws::WebSocket, user: &str) -> ws::Stream!['_] { -/// ws::Stream! { ws => -/// yield user.into(); -/// } -/// } -/// ``` -/// -/// Borrow from the request with explicit lifetime: -/// -/// ```rust -/// # use rkt::get; -/// use rkt_ws as ws; -/// -/// #[get("/hello/")] -/// fn ws_hello<'r>(ws: ws::WebSocket, user: &'r str) -> ws::Stream!['r] { -/// ws::Stream! { ws => -/// yield user.into(); -/// } -/// } -/// ``` -/// -/// Emit several messages and short-circuit if the client sends a bad message: -/// -/// ```rust -/// # use rkt::get; -/// use rkt_ws as ws; -/// -/// #[get("/")] -/// fn echo(ws: ws::WebSocket) -> ws::Stream![] { -/// ws::Stream! { ws => -/// for await message in ws { -/// for i in 0..5u8 { -/// yield i.to_string().into(); -/// } -/// -/// yield message?; -/// } -/// } -/// } -/// ``` -/// -#[macro_export] -macro_rules! Stream { - () => ($crate::Stream!['static]); - ($l:lifetime) => ( - $crate::stream::MessageStream<$l, impl rkt::futures::Stream< - Item = $crate::result::Result<$crate::Message> - > + $l> - ); - ($channel:ident => $($token:tt)*) => ( - let ws: $crate::WebSocket = $channel; - ws.stream(move |$channel| rkt::async_stream::try_stream! { - $($token)* - }) - ); + #[deprecated(since = "1.3.0", note = "use `rkt::ws::result::Result`")] + pub type Result = rkt::ws::result::Result; } + +// A `macro_rules!` re-export cannot carry a deprecation: the macro expands via +// `$crate` to `rkt::ws`, bypassing this crate entirely. Users reach it through +// the `WebSocket` guard, which warns above. +pub use rkt::ws::Stream; diff --git a/contrib/ws/tests/legacy.rs b/contrib/ws/tests/legacy.rs new file mode 100644 index 00000000..5647f748 --- /dev/null +++ b/contrib/ws/tests/legacy.rs @@ -0,0 +1,18 @@ +//! Ensures the pre-promotion import path and macro remain source compatible. + +// This crate is the deprecated shim; exercising it is the point of this test. +#![allow(deprecated)] + +use rkt_ws::{Stream, WebSocket}; + +#[rkt::get("/")] +fn echo(ws: WebSocket) -> Stream![] { + Stream! { ws => + yield "legacy".into(); + } +} + +#[test] +fn legacy_api_is_available() { + let _ = echo; +} diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs index 3562a281..3292f9f7 100644 --- a/core/codegen/src/lib.rs +++ b/core/codegen/src/lib.rs @@ -19,7 +19,7 @@ //! //! ```toml //! [dependencies] -//! rkt = { version = "1.2.0" } +//! rkt = { version = "1.3.0" } //! ``` //! //! And to import all macros, attributes, and derives via `#[macro_use]` in the diff --git a/core/lib/Cargo.toml b/core/lib/Cargo.toml index 2e12c6f8..e48995e7 100644 --- a/core/lib/Cargo.toml +++ b/core/lib/Cargo.toml @@ -32,9 +32,10 @@ pre-release-replacements = [ { file = "../../website/docs/14-faq.md", search = 'rkt = \{ version = "[0-9][0-9A-Za-z.-]*"', replace = 'rkt = { version = "{{version}}"' }, { file = "../../website/docs/10-configuration.md", search = 'rkt = \{ version = "[0-9][0-9A-Za-z.-]*"', replace = 'rkt = { version = "{{version}}"' }, { file = "../../website/docs/04-requests.md", search = 'rkt = \{ version = "[0-9][0-9A-Za-z.-]*"', replace = 'rkt = { version = "{{version}}"' }, - { file = "../../contrib/ws/README.md", search = 'package = "rkt_ws", version = "[0-9][0-9A-Za-z.-]*"', replace = 'package = "rkt_ws", version = "{{version}}"' }, { file = "../../contrib/dyn_templates/README.md", search = 'version = "[0-9][0-9A-Za-z.-]*"', replace = 'version = "{{version}}"' }, - { file = "src/lib.rs", search = 'rkt = \{ package = version = "[0-9][0-9A-Za-z.-]*"', replace = 'rkt = { package = version = "{{version}}"' }, + { file = "../../contrib/ws/README.md", search = 'rkt = \{ version = "[0-9][0-9A-Za-z.-]*"', replace = 'rkt = { version = "{{version}}"' }, + { file = "src/ws/mod.rs", search = 'rkt = \{ version = "[0-9][0-9A-Za-z.-]*"', replace = 'rkt = { version = "{{version}}"' }, + { file = "src/lib.rs", search = 'rkt = "[0-9][0-9A-Za-z.-]*"', replace = 'rkt = "{{version}}"' }, { file = "src/lib.rs", search = 'rkt = \{ version = "[0-9][0-9A-Za-z.-]*"', replace = 'rkt = { version = "{{version}}"' }, { file = "src/listener/quic.rs", search = 'rkt = \{ version = "[0-9][0-9A-Za-z.-]*"', replace = 'rkt = { version = "{{version}}"' }, { file = "src/serde/json.rs", search = '\[dependencies\.rkt\]\n//! version = "[0-9][0-9A-Za-z.-]*"', replace = '''[dependencies.rkt] @@ -62,6 +63,7 @@ mtls = ["tls", "x509-parser"] proxy-proto = [] tokio-macros = ["tokio/macros"] trace = ["tracing-subscriber", "tinyvec", "thread_local", "rustls?/logging", "tokio-rustls?/logging", "multer/log", "s2n-quic-h3?/tracing"] +ws = ["tokio-tungstenite"] [dependencies] # Optional serialization dependencies. @@ -102,6 +104,7 @@ tokio-stream = { version = "0.1.6", features = ["signal", "time"] } cookie = { version = "0.18", features = ["percent-encode"] } futures = { version = "0.3.30", default-features = false, features = ["std"] } state = "0.6" +tokio-tungstenite = { version = "0.30", optional = true } # tracing tracing = { version = "0.1.40", default-features = false, features = ["std", "attributes"] } diff --git a/core/lib/src/form/form.rs b/core/lib/src/form/form.rs index 81f2107b..7c536ddc 100644 --- a/core/lib/src/form/form.rs +++ b/core/lib/src/form/form.rs @@ -144,7 +144,7 @@ impl Form { /// # Example /// /// ```rust -/// #[macro_use] extern crate rkt; + /// #[macro_use] extern crate rkt; /// use rkt::form::Form; /// /// #[derive(FromForm)] diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index 8aa7b424..b58dba57 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -25,7 +25,7 @@ //! //! ```toml //! [dependencies] -//! rkt = "1.2.0" +//! rkt = "1.3.0" //! ``` //! //! See the [guide](https://rkt.rs/guide) for more information on how @@ -62,6 +62,7 @@ //! | `json` | No | Support for [JSON (de)serialization]. | //! | `msgpack` | No | Support for [MessagePack (de)serialization]. | //! | `uuid` | No | Support for [UUID value parsing and (de)serialization]. | +//! | `ws` | No | First-class WebSocket support. | //! | `tokio-macros` | No | Enables the `macros` feature in the exported `tokio` | //! | `http3-preview` | No | Experimental preview support for [HTTP/3]. | //! | `proxy-proto` | No | Support for the [PROXY protocol] (v1 and v2). | @@ -70,14 +71,14 @@ //! //! ```toml //! [dependencies] -//! rkt = { version = "1.2.0", features = ["secrets", "tls", "json"] } +//! rkt = { version = "1.3.0", features = ["secrets", "tls", "json"] } //! ``` //! //! Conversely, HTTP/2 can be disabled: //! //! ```toml //! [dependencies] -//! rkt = { version = "1.2.0", default-features = false } +//! rkt = { version = "1.3.0", default-features = false } //! ``` //! //! [subscriber]: crate::trace::subscriber @@ -152,6 +153,9 @@ pub mod shutdown; #[cfg(feature = "tls")] #[cfg_attr(nightly, doc(cfg(feature = "tls")))] pub mod tls; +#[cfg(feature = "ws")] +#[cfg_attr(nightly, doc(cfg(feature = "ws")))] +pub mod ws; mod erased; mod lifecycle; diff --git a/core/lib/src/listener/proxy.rs b/core/lib/src/listener/proxy.rs index 64d58c2b..5d032739 100644 --- a/core/lib/src/listener/proxy.rs +++ b/core/lib/src/listener/proxy.rs @@ -335,7 +335,9 @@ fn parse_preamble(buf: &[u8]) -> io::Result { return parse_v1(buf); } - Err(invalid("connection does not begin with a v1 or v2 preamble")) + Err(invalid( + "connection does not begin with a v1 or v2 preamble", + )) } /// Parse a v1 (human-readable) preamble. The caller has verified that `buf` @@ -455,9 +457,7 @@ fn parse_v2(buf: &[u8]) -> io::Result { return Ok(Preamble::Incomplete); } - let complete = |remote| { - Ok(Preamble::Complete { remote, consumed }) - }; + let complete = |remote| Ok(Preamble::Complete { remote, consumed }); // For LOCAL, the receiver must use the real connection endpoints; for // AF_UNSPEC, the sender provided no usable address. Either way, any @@ -487,7 +487,10 @@ fn parse_v2(buf: &[u8]) -> io::Result { let mut octets = [0u8; 16]; octets.copy_from_slice(&addr[..16]); let port = u16::from_be_bytes([addr[32], addr[33]]); - complete(Remote::Inet(SocketAddr::new(Ipv6Addr::from(octets).into(), port))) + complete(Remote::Inet(SocketAddr::new( + Ipv6Addr::from(octets).into(), + port, + ))) } V2_AF_UNIX => { if addr.len() < 216 { @@ -794,7 +797,11 @@ mod tests { let (remote, buffer) = read_preamble(&mut server).await?; writer.await.unwrap(); - let mut stream = ProxyProtocolStream { inner: server, remote: remote.clone(), buffer }; + let mut stream = ProxyProtocolStream { + inner: server, + remote: remote.clone(), + buffer, + }; let mut rest = Vec::new(); stream.read_to_end(&mut rest).await?; Ok((remote, rest)) @@ -807,7 +814,10 @@ mod tests { input.extend_from_slice(payload); let (remote, rest) = read_from(&input).await.unwrap(); - assert_eq!(remote, Some(Endpoint::Tcp("1.2.3.4:56324".parse().unwrap()))); + assert_eq!( + remote, + Some(Endpoint::Tcp("1.2.3.4:56324".parse().unwrap())) + ); assert_eq!(rest, payload); } @@ -819,14 +829,20 @@ mod tests { input.extend_from_slice(payload); let (mut client, mut server) = tokio::io::duplex(1024); - tokio::io::AsyncWriteExt::write_all(&mut client, &input).await.unwrap(); + tokio::io::AsyncWriteExt::write_all(&mut client, &input) + .await + .unwrap(); drop(client); let (remote, buffer) = read_preamble(&mut server).await.unwrap(); assert_eq!(remote, Some(Endpoint::Tcp("9.8.7.6:1234".parse().unwrap()))); // drain replayed + live bytes through 3-byte reads - let mut stream = ProxyProtocolStream { inner: server, remote, buffer }; + let mut stream = ProxyProtocolStream { + inner: server, + remote, + buffer, + }; let mut rest = Vec::new(); let mut chunk = [0u8; 3]; loop { @@ -856,7 +872,11 @@ mod tests { #[tokio::test] async fn stream_write_passthrough() { let (mut client, server) = tokio::io::duplex(64); - let mut stream = ProxyProtocolStream { inner: server, remote: None, buffer: None }; + let mut stream = ProxyProtocolStream { + inner: server, + remote: None, + buffer: None, + }; use tokio::io::AsyncWriteExt; stream.write_all(b"hello").await.unwrap(); diff --git a/core/lib/src/listener/quic.rs b/core/lib/src/listener/quic.rs index af5e1e6b..80a53d3b 100644 --- a/core/lib/src/listener/quic.rs +++ b/core/lib/src/listener/quic.rs @@ -6,7 +6,7 @@ //! ```toml //! // Add the following to your Cargo.toml: //! [dependencies] -//! rkt = { version = "1.2.0", features = ["http3-preview"] } +//! rkt = { version = "1.3.0", features = ["http3-preview"] } //! //! // In your Rocket.toml or other equivalent config source: //! [default.tls] diff --git a/core/lib/src/request/request.rs b/core/lib/src/request/request.rs index eb5d8366..220b9a85 100644 --- a/core/lib/src/request/request.rs +++ b/core/lib/src/request/request.rs @@ -809,10 +809,7 @@ impl<'r> Request<'r> { pub fn content_type(&self) -> Option<&ContentType> { self.state .content_type - .get_or_init(|| { - self.header("Content-Type") - .and_then(|v| v.parse().ok()) - }) + .get_or_init(|| self.header("Content-Type").and_then(|v| v.parse().ok())) .as_ref() } @@ -833,10 +830,7 @@ impl<'r> Request<'r> { pub fn accept(&self) -> Option<&Accept> { self.state .accept - .get_or_init(|| { - self.header("Accept") - .and_then(|v| v.parse().ok()) - }) + .get_or_init(|| self.header("Accept").and_then(|v| v.parse().ok())) .as_ref() } diff --git a/core/lib/src/response/redirect.rs b/core/lib/src/response/redirect.rs index 57a6eb4e..e9ef4b21 100644 --- a/core/lib/src/response/redirect.rs +++ b/core/lib/src/response/redirect.rs @@ -24,7 +24,7 @@ use crate::response::{self, Responder, Response}; /// valid URI: /// /// ```rust - /// #[macro_use] extern crate rkt; +/// #[macro_use] extern crate rkt; /// use rkt::response::Redirect; /// /// #[get("/hello//")] diff --git a/core/lib/src/serde/json.rs b/core/lib/src/serde/json.rs index e3689a82..599c2685 100644 --- a/core/lib/src/serde/json.rs +++ b/core/lib/src/serde/json.rs @@ -9,7 +9,7 @@ //! //! ```toml //! [dependencies.rkt] -//! version = "1.2.0" +//! version = "1.3.0" //! features = ["json"] //! ``` //! diff --git a/core/lib/src/serde/msgpack.rs b/core/lib/src/serde/msgpack.rs index c85bdc6e..27672525 100644 --- a/core/lib/src/serde/msgpack.rs +++ b/core/lib/src/serde/msgpack.rs @@ -9,7 +9,7 @@ //! //! ```toml //! [dependencies.rkt] -//! version = "1.2.0" +//! version = "1.3.0" //! features = ["msgpack"] //! ``` //! diff --git a/core/lib/src/serde/uuid.rs b/core/lib/src/serde/uuid.rs index ed398617..4a2d6c9f 100644 --- a/core/lib/src/serde/uuid.rs +++ b/core/lib/src/serde/uuid.rs @@ -7,7 +7,7 @@ //! //! ```toml //! [dependencies.rkt] -//! version = "1.2.0" +//! version = "1.3.0" //! features = ["uuid"] //! ``` //! diff --git a/core/lib/src/server.rs b/core/lib/src/server.rs index f189c345..9951e0c0 100644 --- a/core/lib/src/server.rs +++ b/core/lib/src/server.rs @@ -39,8 +39,7 @@ impl Rocket { // `span_debug!` runs its closure even when the subscriber discards // `DEBUG` events, so also check that the level is enabled to avoid // materializing and formatting headers that will never be logged. - let debug_headers = self.config.debug_headers - && tracing::enabled!(tracing::Level::DEBUG); + let debug_headers = self.config.debug_headers && tracing::enabled!(tracing::Level::DEBUG); let request = ErasedRequest::new(self, parts, |rocket, parts| { Request::from_hyp(rocket, parts, connection).unwrap_or_else(|e| e) }); diff --git a/contrib/ws/src/duplex.rs b/core/lib/src/ws/duplex.rs similarity index 87% rename from contrib/ws/src/duplex.rs rename to core/lib/src/ws/duplex.rs index 54c60612..f1c76eba 100644 --- a/contrib/ws/src/duplex.rs +++ b/core/lib/src/ws/duplex.rs @@ -1,12 +1,12 @@ use std::pin::Pin; use std::task::{Context, Poll}; -use rkt::data::IoStream; -use rkt::futures::stream::{FusedStream, Stream}; -use rkt::futures::{Sink, SinkExt, StreamExt}; +use crate::data::IoStream; +use futures::stream::{FusedStream, Stream}; +use futures::{Sink, SinkExt, StreamExt}; -use crate::frame::{CloseFrame, Message}; -use crate::result::{Error, Result}; +use super::frame::{CloseFrame, Message}; +use super::result::{Error, Result}; /// A readable and writeable WebSocket [`Message`] `async` stream. /// @@ -16,7 +16,7 @@ use crate::result::{Error, Result}; /// /// ```rust /// # use rkt::get; -/// # use rkt_ws as ws; +/// # use rkt::ws; /// use rkt::futures::{SinkExt, StreamExt}; /// /// #[get("/echo/manual")] @@ -36,8 +36,8 @@ use crate::result::{Error, Result}; pub struct DuplexStream(tokio_tungstenite::WebSocketStream); impl DuplexStream { - pub(crate) async fn new(stream: IoStream, config: crate::Config) -> Self { - use crate::tungstenite::protocol::Role; + pub(crate) async fn new(stream: IoStream, config: super::Config) -> Self { + use super::tungstenite::protocol::Role; use tokio_tungstenite::WebSocketStream; let inner = WebSocketStream::from_raw_socket(stream, Role::Server, Some(config)); diff --git a/core/lib/src/ws/mod.rs b/core/lib/src/ws/mod.rs new file mode 100644 index 00000000..89e745a8 --- /dev/null +++ b/core/lib/src/ws/mod.rs @@ -0,0 +1,319 @@ +//! WebSocket support for rkt. +//! +//! This module implements support for WebSockets via Rkt's [connection +//! upgrade API](crate::Response#upgrading) and +//! [tungstenite](tokio_tungstenite). +//! +//! # Usage +//! +//! Enable the `ws` feature: +//! +//! ```toml +//! [dependencies] +//! rkt = { version = "1.3.0", features = ["ws"] } +//! ``` +//! +//! Then, use [`WebSocket`] as a request guard in any route and either call +//! [`WebSocket::channel()`] or return a stream via [`Stream!`] or +//! [`WebSocket::stream()`] in the handler. The examples below are equivalent: +//! +//! ```rust +//! # use rkt::get; +//! # use rkt::ws; +//! # +//! #[get("/echo?channel")] +//! fn echo_channel(ws: ws::WebSocket) -> ws::Channel<'static> { +//! use rkt::futures::{SinkExt, StreamExt}; +//! +//! ws.channel(move |mut stream| Box::pin(async move { +//! while let Some(message) = stream.next().await { +//! let _ = stream.send(message?).await; +//! } +//! +//! Ok(()) +//! })) +//! } +//! +//! #[get("/echo?stream")] +//! fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { +//! ws::Stream! { ws => +//! for await message in ws { +//! yield message?; +//! } +//! } +//! } +//! +//! #[get("/echo?compose")] +//! fn echo_compose(ws: ws::WebSocket) -> ws::Stream!['static] { +//! ws.stream(|io| io) +//! } +//! ``` +//! +//! WebSocket connections are configurable via [`WebSocket::config()`]: +//! +//! ```rust +//! # use rkt::get; +//! # use rkt::ws; +//! # +//! #[get("/echo")] +//! fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { +//! let ws = ws.config(ws::Config::default().max_message_size(Some(8192))); +//! +//! ws::Stream! { ws => +//! for await message in ws { +//! yield message?; +//! } +//! } +//! } +//! ``` + +mod tungstenite { + #[doc(inline)] + pub use tokio_tungstenite::tungstenite::*; +} + +mod duplex; +mod websocket; + +pub use self::websocket::{Channel, WebSocket}; + +/// A WebSocket message. +/// +/// A value of this type is typically constructed by calling `.into()` on a +/// supported message type. This includes strings via `&str` and `String` and +/// bytes via `&[u8]` and `Vec`: +/// +/// ```rust +/// # use rkt::get; +/// # use rkt::ws; +/// # +/// #[get("/echo")] +/// fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { +/// ws::Stream! { ws => +/// yield "Hello".into(); +/// yield String::from("Hello").into(); +/// yield (&[1u8, 2, 3][..]).into(); +/// yield vec![1u8, 2, 3].into(); +/// } +/// } +/// ``` +/// +/// Other kinds of messages can be constructed directly: +/// +/// ```rust +/// # use rkt::get; +/// # use rkt::ws; +/// # +/// #[get("/echo")] +/// fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { +/// ws::Stream! { ws => +/// yield ws::Message::Ping(vec![b'h', b'i'].into()) +/// } +/// } +/// ``` +pub use self::tungstenite::Message; + +/// WebSocket connection configuration. +/// +/// The default configuration for a [`WebSocket`] can be changed by calling +/// [`WebSocket::config()`] with a value of this type. The defaults are obtained +/// via [`Default::default()`]. You don't generally need to reconfigure a +/// `WebSocket` unless you're certain you need different values. In other words, +/// this structure should rarely be used. +/// +/// # Example +/// +/// ```rust +/// # use rkt::get; +/// # use rkt::ws; +/// use rkt::data::ToByteUnit; +/// +/// #[get("/echo")] +/// fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { +/// let ws = ws.config(ws::Config::default() +/// // Decrease the maximum (complete) message size to 4MiB. +/// .max_message_size(Some(4.mebibytes().as_u64() as usize)) +/// // Decrease the maximum size of _one_ frame (not message) to 1MiB. +/// .max_frame_size(Some(1.mebibytes().as_u64() as usize))); +/// +/// ws::Stream! { ws => +/// for await message in ws { +/// yield message?; +/// } +/// } +/// } +/// ``` +/// +/// **Original `tungstenite` Documentation Follows** +/// +pub use self::tungstenite::protocol::WebSocketConfig as Config; + +/// Structures for constructing raw WebSocket frames. +pub mod frame { + pub use super::tungstenite::protocol::frame::coding::CloseCode; + pub use super::tungstenite::protocol::frame::{CloseFrame, Frame}; + #[doc(hidden)] + pub use super::Message; +} + +/// Types representing incoming and/or outgoing `async` [`Message`] streams. +pub mod stream { + pub use super::duplex::DuplexStream; + pub use super::websocket::MessageStream; +} + +/// Library [`Error`](self::result::Error) and +/// [`Result`](self::result::Result) types. +pub mod result { + pub use super::tungstenite::error::{Error, Result}; +} + +/// Type and expression macro for `async` WebSocket [`Message`] streams. +/// +/// This macro can be used both where types are expected or +/// where expressions are expected. +/// +/// # Type Position +/// +/// When used in a type position, the macro invoked as `Stream['r]` expands to: +/// +/// - [`MessageStream`]`<'r, impl `[`Stream`]`>> + 'r>` +/// +/// The lifetime need not be specified as `'r`. For instance, `Stream['request]` +/// is valid and expands as expected: +/// +/// - [`MessageStream`]`<'request, impl `[`Stream`]`>> + 'request>` +/// +/// As a convenience, when the macro is invoked as `Stream![]`, the lifetime +/// defaults to `'static`. That is, `Stream![]` is equivalent to +/// `Stream!['static]`. +/// +/// [`MessageStream`]: self::stream::MessageStream +/// [`Stream`]: crate::futures::stream::Stream +/// [`Result`]: self::result::Result +/// [`Message`]: self::Message +/// +/// # Expression Position +/// +/// When invoked as an expression, the macro behaves similarly to rkt's +/// [`stream!`](crate::response::stream::stream) macro. Specifically, it +/// supports `yield` and `for await` syntax. It is invoked as follows: +/// +/// ```rust +/// # use rkt::get; +/// use rkt::ws; +/// +/// #[get("/")] +/// fn echo(ws: ws::WebSocket) -> ws::Stream![] { +/// ws::Stream! { ws => +/// for await message in ws { +/// yield message?; +/// yield "foo".into(); +/// yield vec![1, 2, 3, 4].into(); +/// } +/// } +/// } +/// ``` +/// +/// It enjoins the following type requirements: +/// +/// * The type of `ws` _must_ be [`WebSocket`]. `ws` can be any ident. +/// * The type of yielded expressions (`expr` in `yield expr`) _must_ be [`Message`]. +/// * The `Err` type of expressions short-circuited with `?` _must_ be [`Error`]. +/// +/// [`Error`]: self::result::Error +/// +/// The macro takes any series of statements and expands them into an expression +/// of type `impl Stream>`, a stream that `yield`s elements of +/// type [`Result`]``. It automatically converts yielded items of type `T` into +/// `Ok(T)`. It supports any Rust statement syntax with the following +/// extensions: +/// +/// * `?` short-circuits stream termination on `Err` +/// +/// The type of the error value must be [`Error`]. +///

+/// +/// * `yield expr` +/// +/// Yields the result of evaluating `expr` to the caller (the stream +/// consumer) wrapped in `Ok`. +/// +/// `expr` must be of type `T`. +///

+/// +/// * `for await x in stream { .. }` +/// +/// `await`s the next element in `stream`, binds it to `x`, and executes the +/// block with the binding. +/// +/// `stream` must implement `Stream`; the type of `x` is `T`. +/// +/// ### Examples +/// +/// Borrow from the request. Send a single message and close: +/// +/// ```rust +/// # use rkt::get; +/// use rkt::ws; +/// +/// #[get("/hello/")] +/// fn ws_hello(ws: ws::WebSocket, user: &str) -> ws::Stream!['_] { +/// ws::Stream! { ws => +/// yield user.into(); +/// } +/// } +/// ``` +/// +/// Borrow from the request with explicit lifetime: +/// +/// ```rust +/// # use rkt::get; +/// use rkt::ws; +/// +/// #[get("/hello/")] +/// fn ws_hello<'r>(ws: ws::WebSocket, user: &'r str) -> ws::Stream!['r] { +/// ws::Stream! { ws => +/// yield user.into(); +/// } +/// } +/// ``` +/// +/// Emit several messages and short-circuit if the client sends a bad message: +/// +/// ```rust +/// # use rkt::get; +/// use rkt::ws; +/// +/// #[get("/")] +/// fn echo(ws: ws::WebSocket) -> ws::Stream![] { +/// ws::Stream! { ws => +/// for await message in ws { +/// for i in 0..5u8 { +/// yield i.to_string().into(); +/// } +/// +/// yield message?; +/// } +/// } +/// } +/// ``` +/// +#[macro_export] +macro_rules! Stream { + () => ($crate::ws::Stream!['static]); + ($l:lifetime) => ( + $crate::ws::stream::MessageStream<$l, impl rkt::futures::Stream< + Item = $crate::ws::result::Result<$crate::ws::Message> + > + $l> + ); + ($channel:ident => $($token:tt)*) => ( + let ws: $crate::ws::WebSocket = $channel; + ws.stream(move |$channel| rkt::async_stream::try_stream! { + $($token)* + }) + ); +} + +#[doc(inline)] +pub use crate::Stream; diff --git a/contrib/ws/src/websocket.rs b/core/lib/src/ws/websocket.rs similarity index 91% rename from contrib/ws/src/websocket.rs rename to core/lib/src/ws/websocket.rs index 8329a45a..4d3a4e89 100644 --- a/contrib/ws/src/websocket.rs +++ b/core/lib/src/ws/websocket.rs @@ -1,14 +1,14 @@ use std::io; -use rkt::data::{IoHandler, IoStream}; -use rkt::futures::{self, future::BoxFuture, stream::SplitStream, SinkExt, StreamExt}; -use rkt::http::Status; -use rkt::request::{FromRequest, Outcome, Request}; -use rkt::response::{self, Responder, Response}; +use crate::data::{IoHandler, IoStream}; +use futures::{future::BoxFuture, stream::SplitStream, SinkExt, StreamExt}; +use crate::http::Status; +use crate::request::{FromRequest, Outcome, Request}; +use crate::response::{self, Responder, Response}; -use crate::result::{Error, Result}; -use crate::stream::DuplexStream; -use crate::{Config, Message}; +use super::result::{Error, Result}; +use super::stream::DuplexStream; +use super::{Config, Message}; /// A request guard identifying WebSocket requests. Converts into a [`Channel`] /// or [`MessageStream`]. @@ -21,7 +21,7 @@ use crate::{Config, Message}; /// initiate via the `WebSocket` request guard. The guard identifies valid /// WebSocket connection requests and, if the request is valid, succeeds to be /// converted into a streaming WebSocket response via -/// [`Stream!`](crate::Stream!), [`WebSocket::channel()`], or +/// [`Stream!`](super::Stream!), [`WebSocket::channel()`], or /// [`WebSocket::stream()`]. The connection can be configured via /// [`WebSocket::config()`]; see [`Config`] for details on configuring a /// connection. @@ -42,7 +42,7 @@ impl WebSocket { /// /// ```rust /// # use rkt::get; - /// # use rkt_ws as ws; + /// # use rkt::ws; /// # /// #[get("/echo")] /// fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { @@ -69,7 +69,7 @@ impl WebSocket { /// The `handler` must return a `Box`ed and `Pin`ned future: calling /// [`Box::pin()`] with a future does just this as is the preferred /// mechanism to create a `Box>`. The future must return a - /// [`Result<()>`](crate::result::Result). The WebSocket connection is + /// [`Result<()>`](super::result::Result). The WebSocket connection is /// closed successfully if the future returns `Ok` and with an error if /// the future returns `Err`. /// @@ -83,7 +83,7 @@ impl WebSocket { /// /// ```rust /// # use rkt::get; - /// # use rkt_ws as ws; + /// # use rkt::ws; /// use rkt::futures::{SinkExt, StreamExt}; /// /// #[get("/hello/")] @@ -120,7 +120,7 @@ impl WebSocket { /// /// This method takes a `FnOnce` `stream` that consumes a read-only stream /// and returns a stream of [`Message`]s. While the returned stream can be - /// constructed in any manner, the [`Stream!`](crate::Stream!) macro is the + /// constructed in any manner, the [`Stream!`](super::Stream!) macro is the /// preferred method. In any case, the stream must be `Send`. /// /// The returned stream must emit items of type `Result`. Items @@ -132,7 +132,7 @@ impl WebSocket { /// /// ```rust /// # use rkt::get; - /// # use rkt_ws as ws; + /// # use rkt::ws; /// /// // Use `Stream!`, which internally calls `WebSocket::stream()`. /// #[get("/echo?stream")] @@ -180,7 +180,7 @@ impl WebSocket { /// /// ```rust /// # use rkt::get; - /// # use rkt_ws as ws; + /// # use rkt::ws; /// # /// #[get("/echo")] /// fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { @@ -208,20 +208,20 @@ pub struct Channel<'r> { /// [`Stream!`] macro, which expands to both the type itself and an expression /// which evaluates to this type. See [`Stream!`] for details. /// -/// [`Stream!`]: crate::Stream! +/// [`Stream!`]: super::Stream! // TODO: Get rid of this or `Channel` via a single `enum`. pub struct MessageStream<'r, S> { ws: WebSocket, handler: Box) -> S + Send + 'r>, } -#[rkt::async_trait] +#[crate::async_trait] impl<'r> FromRequest<'r> for WebSocket { type Error = std::convert::Infallible; async fn from_request(req: &'r Request<'_>) -> Outcome { - use crate::tungstenite::handshake::derive_accept_key; - use rkt::http::uncased::eq; + use super::tungstenite::handshake::derive_accept_key; + use crate::http::uncased::eq; let headers = req.headers(); let is_upgrade = headers @@ -269,7 +269,7 @@ where } } -#[rkt::async_trait] +#[crate::async_trait] impl IoHandler for Channel<'_> { async fn io(self: Box, io: IoStream) -> io::Result<()> { let stream = DuplexStream::new(io, self.ws.config).await; @@ -278,7 +278,7 @@ impl IoHandler for Channel<'_> { } } -#[rkt::async_trait] +#[crate::async_trait] impl<'r, S> IoHandler for MessageStream<'r, S> where S: futures::Stream> + Send + 'r, @@ -286,7 +286,7 @@ where async fn io(self: Box, io: IoStream) -> io::Result<()> { let (mut sink, source) = DuplexStream::new(io, self.ws.config).await.split(); let stream = (self.handler)(source); - rkt::tokio::pin!(stream); + tokio::pin!(stream); while let Some(msg) = stream.next().await { let result = match msg { Ok(msg) if msg.is_close() => return Ok(()), diff --git a/docs/tests/Cargo.toml b/docs/tests/Cargo.toml index df30b211..b9c5f528 100644 --- a/docs/tests/Cargo.toml +++ b/docs/tests/Cargo.toml @@ -10,10 +10,10 @@ missing_docs = "allow" missing_abi = "allow" [dependencies] -rkt = { workspace = true, features = ["secrets"] } +rkt = { workspace = true, features = ["secrets", "ws"] } [dev-dependencies] -rkt = { workspace = true, features = ["secrets", "json", "mtls"] } +rkt = { workspace = true, features = ["secrets", "json", "mtls", "ws"] } figment = { version = "0.10.17", features = ["toml", "env"] } tokio = { version = "1", features = ["macros", "io-std"] } rand = "0.10" @@ -28,6 +28,3 @@ features = ["sqlite", "macros", "migrate", "runtime-tokio"] [dev-dependencies.rkt_dyn_templates] path = "../../contrib/dyn_templates" features = ["tera"] - -[dev-dependencies.rkt_ws] -path = "../../contrib/ws" diff --git a/examples/README.md b/examples/README.md index e6c4f5f6..dd88f63b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -87,5 +87,5 @@ This directory contains projects showcasing Rocket's features. * **[`tls`](./tls)** - Illustrates configuring TLS with a variety of key pair kinds. - * **[`upgrade`](./upgrade)** - Uses the connection upgrade API to implement - WebSocket support using tungstenite. + * **[`upgrade`](./upgrade)** - Uses `rkt::ws` (via the `ws` feature) to + implement a WebSocket echo server over the connection upgrade API. diff --git a/examples/upgrade/Cargo.toml b/examples/upgrade/Cargo.toml index 2305ee0e..6d9056a1 100644 --- a/examples/upgrade/Cargo.toml +++ b/examples/upgrade/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -rkt = { path = "../../core/lib" } -ws = { package = "rkt_ws", path = "../../contrib/ws" } +rkt = { path = "../../core/lib", features = ["ws"] } diff --git a/examples/upgrade/src/main.rs b/examples/upgrade/src/main.rs index 0ebc2ebd..9bf3e75b 100644 --- a/examples/upgrade/src/main.rs +++ b/examples/upgrade/src/main.rs @@ -2,6 +2,7 @@ use rkt::fs::{self, FileServer}; use rkt::futures::{SinkExt, StreamExt}; +use rkt::ws; #[get("/echo?stream", rank = 1)] fn echo_stream(ws: ws::WebSocket) -> ws::Stream!['static] { diff --git a/release.toml b/release.toml index 23731be4..d8401ff1 100644 --- a/release.toml +++ b/release.toml @@ -7,7 +7,10 @@ # core/lib/Cargo.toml so they run exactly once (cargo-release otherwise applies # workspace-level replacements once per crate, resolving paths per-crate). # -# Release a new version with, e.g.: cargo release 1.0.2 --execute --no-publish +# Release a new version with, e.g.: +# +# cargo release 1.3.0 --no-publish --no-tag --no-push --execute +# # (omit --execute for a dry run). # Bump every crate in the workspace together. diff --git a/scripts/test.sh b/scripts/test.sh index 37985b80..07aa7b08 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -87,19 +87,16 @@ function test_contrib() { minijinja ) - WS_FEATURES=( - tungstenite - ) - for feature in "${DYN_TEMPLATES_FEATURES[@]}"; do echo ":: Building and testing dyn_templates [$feature]..." $CARGO test -p rkt_dyn_templates --no-default-features --features $feature $@ done - for feature in "${WS_FEATURES[@]}"; do - echo ":: Building and testing ws [$feature]..." - $CARGO test -p rkt_ws --no-default-features --features $feature $@ - done + # `rkt_ws` is a deprecated shim over `rkt::ws`; its `tungstenite` feature is a + # no-op alias. Check both feature states so the alias can't break consumers. + echo ":: Building and testing ws compatibility shim..." + $CARGO test -p rkt_ws $@ + $CARGO test -p rkt_ws --no-default-features $@ } function test_core() { @@ -114,6 +111,7 @@ function test_core() { json msgpack uuid + ws trace ) diff --git a/website/docs/02-getting-started.md b/website/docs/02-getting-started.md index c2d3127e..805e97c2 100644 --- a/website/docs/02-getting-started.md +++ b/website/docs/02-getting-started.md @@ -38,7 +38,7 @@ Now, add rkt as a dependency in your `Cargo.toml`: ```toml [dependencies] -rkt = "1.2.0" +rkt = "1.3.0" ``` Modify `src/main.rs` so that it contains the code for the rkt `Hello, world!` diff --git a/website/docs/04-requests.md b/website/docs/04-requests.md index c471d71a..a52810a2 100644 --- a/website/docs/04-requests.md +++ b/website/docs/04-requests.md @@ -667,7 +667,7 @@ feature: ```toml ## in Cargo.toml -rkt = { version = "1.2.0", features = ["secrets"] } +rkt = { version = "1.3.0", features = ["secrets"] } ``` The API for retrieving, adding, and removing private cookies is identical except @@ -847,7 +847,7 @@ docs](https://docs.rs/rkt/latest/rkt/#features) for a list of available features feature can be enabled in the `Cargo.toml`: ```toml -rkt = { version = "1.2.0", features = ["json"] } +rkt = { version = "1.3.0", features = ["json"] } ``` ::: diff --git a/website/docs/06-responses.md b/website/docs/06-responses.md index 150b5ecd..eebbc4af 100644 --- a/website/docs/06-responses.md +++ b/website/docs/06-responses.md @@ -440,13 +440,12 @@ how to detect and handle graceful shutdown requests. ### WebSockets -Enabled by Rocket's support for [HTTP connection upgrades], the official -[`rkt_ws`] crate implements first-class support for WebSockets. Working with -`rkt_ws` to implement an echo server looks like this: +Enable rkt's built-in WebSocket support with `features = ["ws"]`. It uses +[HTTP connection upgrades] internally; an echo server looks like this: ```rust # use rkt::get; -use rkt_ws::{WebSocket, Stream}; +use rkt::ws::{WebSocket, Stream}; #[get("/echo")] fn echo_compose(ws: WebSocket) -> Stream!['static] { @@ -454,12 +453,12 @@ fn echo_compose(ws: WebSocket) -> Stream!['static] { } ``` -As with `async` streams, `rkt_ws` also supports using generator syntax for +As with `async` streams, `rkt::ws` also supports using generator syntax for WebSocket messages: ```rust # use rkt::get; -use rkt_ws::{WebSocket, Stream}; +use rkt::ws::{WebSocket, Stream}; #[get("/echo")] fn echo_stream(ws: WebSocket) -> Stream!['static] { @@ -471,10 +470,12 @@ fn echo_stream(ws: WebSocket) -> Stream!['static] { } ``` -For complete usage details, see the [`rkt_ws`] documentation. +For complete usage details, see the [`rkt::ws`] documentation. The former +`rkt_ws` crate is a deprecated compatibility shim for one full minor release +cycle. [HTTP connection upgrades]: https://docs.rs/rkt/latest/rkt/response/struct.Response.html#upgrading -[`rkt_ws`]: https://docs.rs/rkt_ws/latest/rkt_ws/ +[`rkt::ws`]: https://docs.rs/rkt/latest/rkt/ws/ ### JSON diff --git a/website/docs/10-configuration.md b/website/docs/10-configuration.md index 842a1fcc..b01be396 100644 --- a/website/docs/10-configuration.md +++ b/website/docs/10-configuration.md @@ -259,7 +259,7 @@ Security). To enable TLS support: ```toml,ignore [dependencies] - rkt = { version = "1.2.0", features = ["tls"] } + rkt = { version = "1.3.0", features = ["tls"] } ``` 2. Configure a TLS certificate chain and private key via the `tls.key` and @@ -335,7 +335,7 @@ enabled and support configured via the `tls.mutual` config parameter: ```toml,ignore [dependencies] - rkt = { version = "1.2.0", features = ["mtls"] } + rkt = { version = "1.3.0", features = ["mtls"] } ``` This implicitly enables the `tls` feature. diff --git a/website/docs/12-pastebin.md b/website/docs/12-pastebin.md index d4a24569..7e6e561a 100644 --- a/website/docs/12-pastebin.md +++ b/website/docs/12-pastebin.md @@ -58,7 +58,7 @@ Then add the usual rkt dependencies to the `Cargo.toml` file: ```toml [dependencies] -rkt = "1.2.0" +rkt = "1.3.0" ``` And finally, create a skeleton rkt application to work off of in diff --git a/website/docs/14-faq.md b/website/docs/14-faq.md index f1611d4a..87f90a47 100644 --- a/website/docs/14-faq.md +++ b/website/docs/14-faq.md @@ -38,7 +38,8 @@ mainly consists of: The goal is for functionality like templating, sessions, ORMs, and so on to be implemented entirely outside of Rocket while maintaining a first-class feel and -experience. Indeed, crates like [`rkt_dyn_templates`] and [`rkt_ws`] +experience. Indeed, crates like [`rkt_dyn_templates`] and optional modules +like [`rkt::ws`] do just this. As a result, Rocket is neither "bare-bones" nor is it a kitchen sink for all possible features. @@ -75,7 +76,7 @@ trade-off is worth it. Rocket will never compromise security, correctness, or usability to "win" at benchmarks of any sort. [`rkt_dyn_templates`]: https://docs.rs/rkt_dyn_templates/latest/rkt_dyn_templates/ -[`rkt_ws`]: https://docs.rs/rkt_ws/latest/rkt_ws/ +[`rkt::ws`]: https://docs.rs/rkt/latest/rkt/ws/ @@ -276,9 +277,10 @@ Can I, and if so how, do I use WebSockets?
-You can! WebSocket support is provided by the officially maintained -[`rkt_ws`](https://docs.rs/rkt_ws/latest/rkt_ws/) crate. You'll find all the docs you need -there. +You can! Enable the `ws` feature on `rkt` and use +[`rkt::ws`](https://docs.rs/rkt/latest/rkt/ws/). The API documentation there +includes channel and stream examples. `rkt_ws` remains a deprecated migration +shim for one full minor release cycle. Rocket _also_ supports [Server-Sent Events], which allows for real-time _unidirectional_ communication from the server to the client. The protocol is a @@ -641,7 +643,7 @@ is to depend on a `contrib` library from git while also depending on a `crates.io` version of Rocket or vice-versa: ```toml -rkt = { version = "1.2.0" } +rkt = { version = "1.3.0" } rkt_dyn_templates = { git = "https://github.com/rustfoo/rkt.git" } ``` diff --git a/website/docs/index.md b/website/docs/index.md index 4c75acaf..862aa26b 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -6,7 +6,7 @@ sidebar_position: 1 Welcome to rkt! -This is the official guide for rkt v1.2.0. It is designed to serve as a +This is the official guide for rkt v1.3.0. It is designed to serve as a starting point to writing web applications with rkt and Rust. The guide is also designed to be a reference for experienced Rocket developers. diff --git a/website/src/pages/index.js b/website/src/pages/index.js index 637871f2..ac8d9a27 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -49,7 +49,7 @@ const FEATURES = [ { title: 'WebSockets', description: - 'WebSocket support via rkt_ws, using the same handler style as HTTP routes.', + 'Built-in WebSocket support via rkt::ws; enable it with the ws feature.', }, { title: 'Fairings', @@ -70,12 +70,6 @@ const CRATES = [ link: 'https://docs.rs/rkt', linkLabel: 'docs.rs/rkt', }, - { - name: 'rkt_ws', - description: 'WebSocket support.', - link: 'https://docs.rs/rkt_ws', - linkLabel: 'docs.rs/rkt_ws', - }, { name: 'rkt_dyn_templates', description: 'Template rendering via Tera, Handlebars, or MiniJinja.', @@ -179,7 +173,7 @@ function QuickStart() {
- {`[dependencies]\nrkt = "1.2.0"`} + {`[dependencies]\nrkt = "1.3.0"`} {TYPED_EXAMPLE}