diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 28d2b521..fde79f4b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -2,7 +2,7 @@ name: CI env: CARGO_TERM_COLOR: always - MSRV: '1.78' + MSRV: '1.80' on: push: diff --git a/Cargo.toml b/Cargo.toml index ee8b6b9a..77f4d379 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ exclude = [ resolver = "2" [workspace.package] -rust-version = "1.78" +rust-version = "1.80" [workspace.lints.rust] unsafe_code = "forbid" diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index c0e9c470..e54b2ab8 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +# 0.8.9 + +- **added:** `WebSocketUpgrade::{requested_protocols, set_selected_protocol}` for more + flexible subprotocol selection ([#3597]) +- **changed:** Update minimum rust version to 1.80 ([#3620]) + +[#3597]: https://github.com/tokio-rs/axum/pull/3597 +[#3620]: https://github.com/tokio-rs/axum/pull/3620 + # 0.8.8 - Clarify documentation for `Router::route_layer` ([#3567]) diff --git a/axum/README.md b/axum/README.md index bf06b82f..2fb5f089 100644 --- a/axum/README.md +++ b/axum/README.md @@ -104,7 +104,7 @@ This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in ## Minimum supported Rust version -axum's MSRV is 1.78. +axum's MSRV is 1.80. ## Examples diff --git a/axum/src/extract/ws.rs b/axum/src/extract/ws.rs index 85c16bc4..0d9005b4 100644 --- a/axum/src/extract/ws.rs +++ b/axum/src/extract/ws.rs @@ -107,8 +107,10 @@ use hyper_util::rt::TokioIo; use sha1::{Digest, Sha1}; use std::{ borrow::Cow, + collections::BTreeSet, future::Future, pin::Pin, + str, task::{ready, Context, Poll}, }; use tokio_tungstenite::{ @@ -138,7 +140,7 @@ pub struct WebSocketUpgrade { sec_websocket_key: Option, on_upgrade: hyper::upgrade::OnUpgrade, on_failed_upgrade: F, - sec_websocket_protocol: Option, + sec_websocket_protocol: BTreeSet, } impl std::fmt::Debug for WebSocketUpgrade { @@ -242,26 +244,23 @@ impl WebSocketUpgrade { I: IntoIterator, I::Item: Into>, { - if let Some(req_protocols) = self - .sec_websocket_protocol - .as_ref() - .and_then(|p| p.to_str().ok()) - { - self.protocol = protocols - .into_iter() - // FIXME: This will often allocate a new `String` and so is less efficient than it - // could be. But that can't be fixed without breaking changes to the public API. - .map(Into::into) - .find(|protocol| { - req_protocols - .split(',') - .any(|req_protocol| req_protocol.trim() == protocol) - }) - .map(|protocol| match protocol { - Cow::Owned(s) => HeaderValue::from_str(&s).unwrap(), - Cow::Borrowed(s) => HeaderValue::from_static(s), - }); - } + self.protocol = protocols + .into_iter() + .map(Into::into) + .find(|proto| { + // FIXME: When https://github.com/hyperium/http/pull/814 + // is merged + released, we can look use + // `contains(proto.as_bytes())` without converting + // to `HeaderValue` first. + let Ok(proto) = HeaderValue::from_str(proto) else { + return false; + }; + self.sec_websocket_protocol.contains(&proto) + }) + .map(|protocol| match protocol { + Cow::Owned(s) => HeaderValue::from_str(&s).unwrap(), + Cow::Borrowed(s) => HeaderValue::from_static(s), + }); self } @@ -277,13 +276,8 @@ impl WebSocketUpgrade { /// ``` /// /// this method returns an iterator yielding `"soap"` and `"wamp"`. - pub fn requested_protocols(&self) -> impl Iterator { - self.sec_websocket_protocol - .as_ref() - .and_then(|p| p.to_str().ok()) - .into_iter() - .flat_map(|s| s.split(',')) - .map(|s| s.trim()) + pub fn requested_protocols(&self) -> impl Iterator { + self.sec_websocket_protocol.iter() } /// Set the chosen WebSocket subprotocol. @@ -501,7 +495,16 @@ where .remove::() .ok_or(ConnectionNotUpgradable)?; - let sec_websocket_protocol = parts.headers.get(header::SEC_WEBSOCKET_PROTOCOL).cloned(); + let sec_websocket_protocol = parts + .headers + .get_all(header::SEC_WEBSOCKET_PROTOCOL) + .iter() + .flat_map(|val| val.as_bytes().split(|&b| b == b',')) + .map(|proto| { + HeaderValue::from_bytes(proto.trim_ascii()) + .expect("substring of HeaderValue is valid HeaderValue") + }) + .collect(); Ok(Self { config: Default::default(),