1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// FIXME: remove it as soon as the rustc version used in docs.rs is updated
#![cfg_attr(finchers_inject_extern_prelude, feature(extern_prelude))]

//! WebSocket support for Finchers based on tungstenite.
//!
//! # Example
//!
//! ```
//! #[macro_use]
//! extern crate finchers;
//! extern crate finchers_tungstenite;
//! # extern crate futures;
//!
//! use finchers::prelude::*;
//! use finchers_tungstenite::{
//!   Ws,
//!   WsTransport,
//! };
//!
//! # fn main() {
//! let endpoint = path!(@get / "ws" /)
//!     .and(finchers_tungstenite::ws())
//!     .map(|ws: Ws| {
//!         ws.on_upgrade(|ws: WsTransport| {
//!             // ...
//! #           drop(ws);
//! #           futures::future::ok(())
//!         })
//!     });
//! # drop(|| {
//! # finchers::server::start(endpoint)
//! #     .serve("127.0.0.1:4000")
//! #     .unwrap();
//! # });
//! # }
//! ```

#![doc(html_root_url = "https://docs.rs/finchers-tungstenite/0.2.0")]
#![warn(
    missing_docs,
    missing_debug_implementations,
    nonstandard_style,
    rust_2018_idioms,
    unused,
)]
//#![warn(rust_2018_compatibility)]
#![cfg_attr(test, deny(warnings))]
#![cfg_attr(test, doc(test(attr(deny(warnings)))))]

extern crate base64;
#[macro_use]
extern crate failure;
extern crate finchers;
extern crate futures;
extern crate http;
extern crate sha1;
extern crate tokio_tungstenite;

pub extern crate tungstenite;

mod handshake;

// re-exports
pub use self::handshake::{HandshakeError, HandshakeErrorKind};
pub use self::imp::{ws, Ws, WsEndpoint, WsTransport};

#[doc(no_inline)]
pub use tungstenite::error::Error as WsError;
#[doc(no_inline)]
pub use tungstenite::protocol::{Message, WebSocketConfig};

mod imp {
    use finchers;
    use finchers::endpoint::{ApplyContext, ApplyResult, Endpoint};
    use finchers::endpoints::upgrade::{Builder, UpgradedIo};
    use finchers::output::Output;

    use tungstenite::protocol::{Role, WebSocketConfig};

    use futures::{Async, Future, Poll};
    use http::header;
    use tokio_tungstenite::WebSocketStream;

    use handshake::{handshake, Accept};

    #[allow(missing_docs)]
    pub type WsTransport = WebSocketStream<UpgradedIo>;

    /// Create an endpoint which handles the WebSocket handshake request.
    pub fn ws() -> WsEndpoint {
        (WsEndpoint { _priv: () }).with_output::<(Ws,)>()
    }

    /// An instance of `Endpoint` which handles the WebSocket handshake request.
    #[derive(Debug, Copy, Clone)]
    pub struct WsEndpoint {
        _priv: (),
    }

    impl<'a> Endpoint<'a> for WsEndpoint {
        type Output = (Ws,);
        type Future = WsFuture;

        fn apply(&'a self, _: &mut ApplyContext<'_>) -> ApplyResult<Self::Future> {
            Ok(WsFuture { _priv: () })
        }
    }

    #[derive(Debug)]
    pub struct WsFuture {
        _priv: (),
    }

    impl Future for WsFuture {
        type Item = (Ws,);
        type Error = finchers::error::Error;

        fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
            let accept = finchers::endpoint::with_get_cx(|cx| handshake(cx))?;
            Ok(Async::Ready((Ws {
                builder: Builder::new(),
                accept,
                config: None,
            },)))
        }
    }

    /// A type representing the result of handshake handling.
    ///
    /// The value of this type is used to build a WebSocket process
    /// after upgrading the protocol.
    #[derive(Debug)]
    pub struct Ws {
        builder: Builder,
        accept: Accept,
        config: Option<WebSocketConfig>,
    }

    impl Ws {
        /// Sets the configuration for upgraded WebSocket connection.
        pub fn config(self, config: WebSocketConfig) -> Ws {
            Ws {
                config: Some(config),
                ..self
            }
        }

        /// Creates an `Output` with the specified function which constructs
        /// a `Future` representing the task after upgrading the protocol to
        /// WebSocket.
        pub fn on_upgrade<F, R>(self, upgrade: F) -> impl Output
        where
            F: FnOnce(WsTransport) -> R + Send + 'static,
            R: Future<Item = (), Error = ()> + Send + 'static,
        {
            let Self {
                builder,
                accept,
                config,
            } = self;

            builder
                .header(header::CONNECTION, "upgrade")
                .header(header::UPGRADE, "websocket")
                .header(header::SEC_WEBSOCKET_ACCEPT, &*accept.hash)
                .finish(move |upgraded| {
                    let ws_stream =
                        WebSocketStream::from_raw_socket(upgraded, Role::Server, config);
                    upgrade(ws_stream)
                })
        }
    }
}