├── .gitignore ├── src ├── lib.rs ├── websocket_connection.rs └── handler.rs ├── Cargo.toml ├── examples └── example.rs ├── .github ├── workflows │ └── ci.yaml ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! it's websockets, for tide! 2 | //! 3 | //! see [`WebSocket`] for examples and usage 4 | 5 | #![forbid(unsafe_code, future_incompatible)] 6 | #![deny( 7 | missing_debug_implementations, 8 | nonstandard_style, 9 | missing_docs, 10 | unreachable_pub, 11 | missing_copy_implementations, 12 | unused_qualifications 13 | )] 14 | 15 | mod handler; 16 | mod websocket_connection; 17 | 18 | pub use handler::WebSocket; 19 | pub use websocket_connection::WebSocketConnection; 20 | 21 | pub use async_tungstenite; 22 | pub use async_tungstenite::tungstenite::{self, Error, Message}; 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tide-websockets" 3 | version = "0.5.0" 4 | authors = ["Jacob Rothstein "] 5 | edition = "2018" 6 | description = "tide websockets" 7 | repository = "https://github.com/http-rs/tide-websockets" 8 | license = "MIT OR Apache-2.0" 9 | keywords = ["tide", "websockets"] 10 | categories = ["web-programming::http-server", "web-programming"] 11 | 12 | [dependencies] 13 | async-dup = "1.2.2" 14 | async-std = "1.9.0" 15 | async-tungstenite = "0.22.2" 16 | base64 = "0.21.0" 17 | futures-util = "0.3.15" 18 | serde = "1.0.126" 19 | serde_json = "1.0.64" 20 | sha1 = "0.10.5" 21 | tide = { version = "0.16.0", default-features = false, features = ["h1-server"] } 22 | 23 | [dev-dependencies] 24 | async-std = { version = "1.9.0", features = ["attributes"] } 25 | env_logger = "0.10.0" 26 | -------------------------------------------------------------------------------- /examples/example.rs: -------------------------------------------------------------------------------- 1 | use async_std::prelude::*; 2 | use tide_websockets::{Message, WebSocket}; 3 | 4 | #[async_std::main] 5 | async fn main() -> Result<(), std::io::Error> { 6 | env_logger::init(); 7 | let mut app = tide::new(); 8 | app.at("/as_middleware") 9 | .with(WebSocket::new(|_request, mut stream| async move { 10 | while let Some(Ok(Message::Text(input))) = stream.next().await { 11 | let output: String = input.chars().rev().collect(); 12 | 13 | stream 14 | .send_string(format!("{} | {}", &input, &output)) 15 | .await?; 16 | } 17 | 18 | Ok(()) 19 | })) 20 | .get(|_| async move { Ok("this was not a websocket request") }); 21 | 22 | app.at("/as_endpoint") 23 | .get(WebSocket::new(|_request, mut stream| async move { 24 | while let Some(Ok(Message::Text(input))) = stream.next().await { 25 | let output: String = input.chars().rev().collect(); 26 | 27 | stream 28 | .send_string(format!("{} | {}", &input, &output)) 29 | .await?; 30 | } 31 | 32 | Ok(()) 33 | })); 34 | 35 | app.listen("127.0.0.1:8080").await?; 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | env: 10 | RUSTFLAGS: -Dwarnings 11 | 12 | jobs: 13 | build_and_test: 14 | name: Build and test 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, windows-latest, macOS-latest] 19 | rust: [stable, nightly] 20 | 21 | steps: 22 | - uses: actions/checkout@main 23 | 24 | - name: Install ${{ matrix.rust }} 25 | uses: actions-rs/toolchain@v1 26 | with: 27 | toolchain: ${{ matrix.rust }} 28 | override: true 29 | 30 | - name: check 31 | uses: actions-rs/cargo@v1 32 | with: 33 | command: check 34 | args: --all --bins --examples 35 | 36 | - name: check avoid-dev-deps 37 | uses: actions-rs/cargo@v1 38 | if: matrix.rust == 'nightly' 39 | with: 40 | command: check 41 | args: --all -Z avoid-dev-deps 42 | 43 | - name: tests 44 | uses: actions-rs/cargo@v1 45 | with: 46 | command: test 47 | args: --all 48 | 49 | check_fmt_and_docs: 50 | name: Checking fmt, clippy, and docs 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@main 54 | 55 | - uses: actions-rs/toolchain@v1 56 | with: 57 | toolchain: stable 58 | override: true 59 | 60 | - name: setup 61 | run: | 62 | rustup component add clippy rustfmt 63 | rustc --version 64 | 65 | - name: clippy 66 | run: cargo clippy -- -D warnings 67 | 68 | - name: fmt 69 | run: cargo fmt --all -- --check 70 | 71 | - name: Docs 72 | run: cargo doc --no-deps 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tide-websockets 2 | 3 | 4 | ## experimental websockets handler for [tide](https://github.com/http-rs/tide) based on [async-tungstenite](https://github.com/sdroege/async-tungstenite) 5 | 6 | * [CI ![CI][ci-badge]][ci] 7 | * [API Docs][docs] [![docs.rs docs][docs-badge]][docs] 8 | * [Releases][releases] [![crates.io version][version-badge]][lib-rs] 9 | 10 | [ci]: https://github.com/http-rs/tide-websockets/actions?query=workflow%3ACI 11 | [ci-badge]: https://github.com/http-rs/tide-websockets/workflows/CI/badge.svg 12 | [releases]: https://github.com/http-rs/tide-websockets/releases 13 | [docs]: https://docs.rs/tide-websockets 14 | [lib-rs]: https://lib.rs/tide-websockets 15 | [docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square 16 | [version-badge]: https://img.shields.io/crates/v/tide-websockets.svg?style=flat-square 17 | 18 | ## Installation 19 | ```sh 20 | $ cargo add tide-websockets 21 | ``` 22 | 23 | ## Using with tide 24 | 25 | This can either be used as a middleware or as an endpoint. If used as a middleware, the endpoint will be executed if the request is not a websocket upgrade. If used as an endpoint but the request is not a websocket request, tide will reply with a `426 Upgrade Required` status code. 26 | 27 | see [the example](https://github.com/http-rs/tide-websockets/blob/main/examples/example.rs) for the most up-to-date example of usage 28 | 29 | ## Safety 30 | This crate uses ``#![deny(unsafe_code)]`` to ensure everything is implemented in 31 | 100% Safe Rust. 32 | 33 | ## Alternatives 34 | - [tide-websockets-sink](https://github.com/cryptoquick/tide-websockets-sink) - A fork of this project that implements the Sink trait. 35 | 36 | ## License 37 | 38 | 39 | Licensed under either of Apache License, Version 40 | 2.0 or MIT license at your option. 41 | 42 | 43 |
44 | 45 | 46 | Unless you explicitly state otherwise, any contribution intentionally submitted 47 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 48 | be dual licensed as above, without any additional terms or conditions. 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/websocket_connection.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | 3 | use async_dup::{Arc, Mutex}; 4 | use async_std::task; 5 | use async_tungstenite::WebSocketStream; 6 | use futures_util::stream::{SplitSink, SplitStream, Stream}; 7 | use futures_util::{SinkExt, StreamExt}; 8 | 9 | use crate::Message; 10 | use tide::http::upgrade::Connection; 11 | 12 | /// # WebSocket connection 13 | /// 14 | /// This is the type that the handler passed to [`WebSocket::new`] 15 | /// receives. It represents a bidirectional stream of websocket data. 16 | #[derive(Clone, Debug)] 17 | pub struct WebSocketConnection( 18 | Arc, Message>>>, 19 | Arc>>>, 20 | ); 21 | 22 | impl WebSocketConnection { 23 | /// Sends a string message to the connected websocket client. This is equivalent to `.send(Message::Text(string))` 24 | pub async fn send_string(&self, string: String) -> async_tungstenite::tungstenite::Result<()> { 25 | self.send(Message::Text(string)).await 26 | } 27 | 28 | /// Sends a binary message to the connected websocket client. This is equivalent to `.send(Message::Binary(bytes))` 29 | pub async fn send_bytes(&self, bytes: Vec) -> async_tungstenite::tungstenite::Result<()> { 30 | self.send(Message::Binary(bytes)).await 31 | } 32 | 33 | /// Sends a [`Message`] to the client 34 | pub async fn send(&self, message: Message) -> async_tungstenite::tungstenite::Result<()> { 35 | self.0.lock().send(message).await?; 36 | Ok(()) 37 | } 38 | 39 | /// Sends the serde_json serialization of the provided type as a string to the connected websocket client 40 | pub async fn send_json(&self, json: &impl serde::Serialize) -> tide::Result<()> { 41 | self.send_string(serde_json::to_string(json)?).await?; 42 | Ok(()) 43 | } 44 | 45 | pub(crate) fn new(ws: WebSocketStream) -> Self { 46 | let (s, r) = ws.split(); 47 | Self(Arc::new(Mutex::new(s)), Arc::new(Mutex::new(r))) 48 | } 49 | } 50 | 51 | impl Stream for WebSocketConnection { 52 | type Item = Result; 53 | 54 | fn poll_next( 55 | self: Pin<&mut Self>, 56 | cx: &mut task::Context<'_>, 57 | ) -> task::Poll> { 58 | Pin::new(&mut *self.1.lock()).poll_next(cx) 59 | } 60 | } 61 | 62 | impl From> for WebSocketConnection { 63 | fn from(ws: WebSocketStream) -> Self { 64 | Self::new(ws) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Contributions include code, documentation, answering user questions, running the 3 | project's infrastructure, and advocating for all types of users. 4 | 5 | The project welcomes all contributions from anyone willing to work in good faith 6 | with other contributors and the community. No contribution is too small and all 7 | contributions are valued. 8 | 9 | This guide explains the process for contributing to the project's GitHub 10 | Repository. 11 | 12 | - [Code of Conduct](#code-of-conduct) 13 | - [Bad Actors](#bad-actors) 14 | 15 | ## Code of Conduct 16 | The project has a [Code of Conduct](./CODE_OF_CONDUCT.md) that *all* 17 | contributors are expected to follow. This code describes the *minimum* behavior 18 | expectations for all contributors. 19 | 20 | As a contributor, how you choose to act and interact towards your 21 | fellow contributors, as well as to the community, will reflect back not only 22 | on yourself but on the project as a whole. The Code of Conduct is designed and 23 | intended, above all else, to help establish a culture within the project that 24 | allows anyone and everyone who wants to contribute to feel safe doing so. 25 | 26 | Should any individual act in any way that is considered in violation of the 27 | [Code of Conduct](./CODE_OF_CONDUCT.md), corrective actions will be taken. It is 28 | possible, however, for any individual to *act* in such a manner that is not in 29 | violation of the strict letter of the Code of Conduct guidelines while still 30 | going completely against the spirit of what that Code is intended to accomplish. 31 | 32 | Open, diverse, and inclusive communities live and die on the basis of trust. 33 | Contributors can disagree with one another so long as they trust that those 34 | disagreements are in good faith and everyone is working towards a common 35 | goal. 36 | 37 | ## Bad Actors 38 | All contributors to tacitly agree to abide by both the letter and 39 | spirit of the [Code of Conduct](./CODE_OF_CONDUCT.md). Failure, or 40 | unwillingness, to do so will result in contributions being respectfully 41 | declined. 42 | 43 | A *bad actor* is someone who repeatedly violates the *spirit* of the Code of 44 | Conduct through consistent failure to self-regulate the way in which they 45 | interact with other contributors in the project. In doing so, bad actors 46 | alienate other contributors, discourage collaboration, and generally reflect 47 | poorly on the project as a whole. 48 | 49 | Being a bad actor may be intentional or unintentional. Typically, unintentional 50 | bad behavior can be easily corrected by being quick to apologize and correct 51 | course *even if you are not entirely convinced you need to*. Giving other 52 | contributors the benefit of the doubt and having a sincere willingness to admit 53 | that you *might* be wrong is critical for any successful open collaboration. 54 | 55 | Don't be a bad actor. 56 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 121 | 122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 123 | enforcement ladder](https://github.com/mozilla/diversity). 124 | 125 | [homepage]: https://www.contributor-covenant.org 126 | 127 | For answers to common questions about this code of conduct, see the FAQ at 128 | https://www.contributor-covenant.org/faq. Translations are available at 129 | https://www.contributor-covenant.org/translations. 130 | -------------------------------------------------------------------------------- /src/handler.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::marker::{PhantomData, Send}; 3 | 4 | use crate::async_tungstenite::WebSocketStream; 5 | use crate::tungstenite::protocol::Role; 6 | use crate::WebSocketConnection; 7 | 8 | use async_dup::Arc; 9 | use async_std::task; 10 | use base64::{prelude::BASE64_STANDARD, Engine}; 11 | use sha1::{digest::Update, Digest, Sha1}; 12 | 13 | use tide::http::format_err; 14 | use tide::http::headers::{HeaderName, CONNECTION, UPGRADE}; 15 | use tide::{Middleware, Request, Response, Result, StatusCode}; 16 | 17 | const WEBSOCKET_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 18 | 19 | /// # endpoint/middleware handler for websockets in tide 20 | /// 21 | /// This can either be used as a middleware or as an 22 | /// endpoint. Regardless of which approach is taken, the handler 23 | /// function provided to [`WebSocket::new`] is only called if the 24 | /// request correctly negotiates an upgrade to the websocket protocol. 25 | /// 26 | /// ## As a middleware 27 | /// 28 | /// If used as a middleware, the endpoint will be executed if the 29 | /// request is not a websocket upgrade. 30 | /// 31 | /// ### Example 32 | /// 33 | /// ```rust 34 | /// use async_std::prelude::*; 35 | /// use tide_websockets::{Message, WebSocket}; 36 | /// 37 | /// #[async_std::main] 38 | /// async fn main() -> Result<(), std::io::Error> { 39 | /// let mut app = tide::new(); 40 | /// 41 | /// app.at("/ws") 42 | /// .with(WebSocket::new(|_request, mut stream| async move { 43 | /// while let Some(Ok(Message::Text(input))) = stream.next().await { 44 | /// let output: String = input.chars().rev().collect(); 45 | /// 46 | /// stream 47 | /// .send_string(format!("{} | {}", &input, &output)) 48 | /// .await?; 49 | /// } 50 | /// 51 | /// Ok(()) 52 | /// })) 53 | /// .get(|_| async move { Ok("this was not a websocket request") }); 54 | /// 55 | /// # if false { 56 | /// app.listen("127.0.0.1:8080").await?; 57 | /// # } 58 | /// Ok(()) 59 | /// } 60 | /// ``` 61 | /// 62 | /// ## As an endpoint 63 | /// 64 | /// If used as an endpoint but the request is 65 | /// not a websocket request, tide will reply with a `426 Upgrade 66 | /// Required` status code. 67 | /// 68 | /// ### example 69 | /// 70 | /// ```rust 71 | /// use async_std::prelude::*; 72 | /// use tide_websockets::{Message, WebSocket}; 73 | /// 74 | /// #[async_std::main] 75 | /// async fn main() -> Result<(), std::io::Error> { 76 | /// let mut app = tide::new(); 77 | /// 78 | /// app.at("/ws") 79 | /// .get(WebSocket::new(|_request, mut stream| async move { 80 | /// while let Some(Ok(Message::Text(input))) = stream.next().await { 81 | /// let output: String = input.chars().rev().collect(); 82 | /// 83 | /// stream 84 | /// .send_string(format!("{} | {}", &input, &output)) 85 | /// .await?; 86 | /// } 87 | /// 88 | /// Ok(()) 89 | /// })); 90 | /// 91 | /// # if false { 92 | /// app.listen("127.0.0.1:8080").await?; 93 | /// # } 94 | /// Ok(()) 95 | /// } 96 | /// ``` 97 | /// 98 | #[derive(Debug)] 99 | pub struct WebSocket { 100 | handler: Arc, 101 | ghostly_apparition: PhantomData, 102 | protocols: Vec, 103 | } 104 | 105 | enum UpgradeStatus { 106 | Upgraded(Result), 107 | NotUpgraded(Request), 108 | } 109 | use UpgradeStatus::{NotUpgraded, Upgraded}; 110 | 111 | fn header_contains_ignore_case(req: &Request, header_name: HeaderName, value: &str) -> bool { 112 | req.header(header_name) 113 | .map(|h| { 114 | h.as_str() 115 | .split(',') 116 | .any(|s| s.trim().eq_ignore_ascii_case(value.trim())) 117 | }) 118 | .unwrap_or(false) 119 | } 120 | 121 | impl WebSocket 122 | where 123 | S: Send + Sync + Clone + 'static, 124 | H: Fn(Request, WebSocketConnection) -> Fut + Sync + Send + 'static, 125 | Fut: Future> + Send + 'static, 126 | { 127 | /// Build a new WebSocket with a handler function that 128 | pub fn new(handler: H) -> Self { 129 | Self { 130 | handler: Arc::new(handler), 131 | ghostly_apparition: PhantomData, 132 | protocols: Default::default(), 133 | } 134 | } 135 | 136 | /// `protocols` is a sequence of known protocols. On successful handshake, 137 | /// the returned response headers contain the first protocol in this list 138 | /// which the server also knows. 139 | pub fn with_protocols(self, protocols: &[&str]) -> Self { 140 | Self { 141 | protocols: protocols.iter().map(ToString::to_string).collect(), 142 | ..self 143 | } 144 | } 145 | 146 | async fn handle_upgrade(&self, req: Request) -> UpgradeStatus { 147 | let connection_upgrade = header_contains_ignore_case(&req, CONNECTION, "upgrade"); 148 | let upgrade_to_websocket = header_contains_ignore_case(&req, UPGRADE, "websocket"); 149 | let upgrade_requested = connection_upgrade && upgrade_to_websocket; 150 | 151 | if !upgrade_requested { 152 | return NotUpgraded(req); 153 | } 154 | 155 | let header = match req.header("Sec-Websocket-Key") { 156 | Some(h) => h.as_str(), 157 | None => return Upgraded(Err(format_err!("expected sec-websocket-key"))), 158 | }; 159 | 160 | let protocol = req.header("Sec-Websocket-Protocol").and_then(|value| { 161 | value 162 | .as_str() 163 | .split(',') 164 | .map(str::trim) 165 | .find(|req_p| self.protocols.iter().any(|p| p == req_p)) 166 | }); 167 | 168 | let mut response = Response::new(StatusCode::SwitchingProtocols); 169 | 170 | response.insert_header(UPGRADE, "websocket"); 171 | response.insert_header(CONNECTION, "Upgrade"); 172 | let hash = Sha1::new().chain(header).chain(WEBSOCKET_GUID).finalize(); 173 | response.insert_header("Sec-Websocket-Accept", BASE64_STANDARD.encode(&hash[..])); 174 | response.insert_header("Sec-Websocket-Version", "13"); 175 | 176 | if let Some(protocol) = protocol { 177 | response.insert_header("Sec-Websocket-Protocol", protocol); 178 | } 179 | 180 | let http_res: &mut tide::http::Response = response.as_mut(); 181 | let upgrade_receiver = http_res.recv_upgrade().await; 182 | let handler = self.handler.clone(); 183 | 184 | task::spawn(async move { 185 | if let Some(stream) = upgrade_receiver.await { 186 | let stream = WebSocketStream::from_raw_socket(stream, Role::Server, None).await; 187 | handler(req, stream.into()).await 188 | } else { 189 | Err(format_err!("never received an upgrade!")) 190 | } 191 | }); 192 | 193 | Upgraded(Ok(response)) 194 | } 195 | } 196 | 197 | #[tide::utils::async_trait] 198 | impl tide::Endpoint for WebSocket 199 | where 200 | H: Fn(Request, WebSocketConnection) -> Fut + Sync + Send + 'static, 201 | Fut: Future> + Send + 'static, 202 | S: Send + Sync + Clone + 'static, 203 | { 204 | async fn call(&self, req: Request) -> Result { 205 | match self.handle_upgrade(req).await { 206 | Upgraded(result) => result, 207 | NotUpgraded(_) => Ok(Response::new(StatusCode::UpgradeRequired)), 208 | } 209 | } 210 | } 211 | 212 | #[tide::utils::async_trait] 213 | impl Middleware for WebSocket 214 | where 215 | H: Fn(Request, WebSocketConnection) -> Fut + Sync + Send + 'static, 216 | Fut: Future> + Send + 'static, 217 | S: Send + Sync + Clone + 'static, 218 | { 219 | async fn handle(&self, req: Request, next: tide::Next<'_, S>) -> Result { 220 | match self.handle_upgrade(req).await { 221 | Upgraded(result) => result, 222 | NotUpgraded(req) => Ok(next.run(req).await), 223 | } 224 | } 225 | } 226 | --------------------------------------------------------------------------------