diff options
-rw-r--r-- | Cargo.lock | 161 | ||||
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | src/web.rs | 78 | ||||
-rw-r--r-- | src/web/ws.rs | 61 |
4 files changed, 295 insertions, 12 deletions
@@ -103,6 +103,12 @@ dependencies = [ ] [[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[package]] name = "bincode" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -137,7 +143,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" dependencies = [ "arrayref", - "byte-tools", + "byte-tools 0.2.0", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools 0.3.1", + "byteorder", + "generic-array 0.12.3", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools 0.3.1", ] [[package]] @@ -156,6 +183,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" [[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] name = "bytecount" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -435,7 +468,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" dependencies = [ - "generic-array", + "generic-array 0.9.0", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.3", ] [[package]] @@ -660,6 +702,15 @@ dependencies = [ ] [[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[package]] name = "getrandom" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -678,9 +729,8 @@ checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" [[package]] name = "gotham" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb734995f768833f633d6acaee1fe768e1395dafc75362a2dac404ba32d7883a" +version = "0.5.0-dev" +source = "git+https://github.com/gotham-rs/gotham?rev=d2395926b93710832f8d72b49c9bd3e77516e386#d2395926b93710832f8d72b49c9bd3e77516e386" dependencies = [ "base64 0.10.1", "bincode", @@ -789,9 +839,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.12.35" +version = "0.12.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbe6ed1438e1f8ad955a4701e9a944938e9519f6888d12d8558b645e247d5f6" +checksum = "7cb44cbce9d8ee4fb36e4c0ad7b794ac44ebaad924b9c8291a63215bb44c2c8f" dependencies = [ "bytes", "futures", @@ -868,6 +918,15 @@ dependencies = [ ] [[package]] +name = "input_buffer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1b822cc844905551931d6f81608ed5f50a79c1078a4e2b4d42dbc7c1eedfbf" +dependencies = [ + "bytes", +] + +[[package]] name = "iovec" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1123,6 +1182,12 @@ dependencies = [ ] [[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] name = "open" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1719,14 +1784,32 @@ dependencies = [ ] [[package]] +name = "sha-1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] name = "sha2" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" dependencies = [ - "block-buffer", - "byte-tools", - "digest", + "block-buffer 0.3.3", + "byte-tools 0.2.0", + "digest 0.7.6", "fake-simd", ] @@ -1869,6 +1952,7 @@ dependencies = [ name = "teleterm" version = "0.2.0" dependencies = [ + "base64 0.11.0", "bytes", "clap", "component-future", @@ -1878,6 +1962,7 @@ dependencies = [ "env_logger", "futures", "gotham", + "hyper", "lazy_static", "log", "mio", @@ -1889,12 +1974,14 @@ dependencies = [ "regex", "reqwest", "serde", + "sha1", "snafu", "tokio", "tokio-pty-process-stream", "tokio-signal", "tokio-terminal-resize", "tokio-tls", + "tokio-tungstenite", "ttyrec", "twoway", "url 2.1.0", @@ -2034,6 +2121,18 @@ dependencies = [ ] [[package]] +name = "tokio-dns-unofficial" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb9bf62ca2c53bf2f2faec3e48a98b6d8c9577c27011cb0203a4beacdc8ab328" +dependencies = [ + "futures", + "futures-cpupool", + "lazy_static", + "tokio", +] + +[[package]] name = "tokio-executor" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2221,6 +2320,22 @@ dependencies = [ ] [[package]] +name = "tokio-tungstenite" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f95da5281a1a52e72fa3657e571279bcc2b163ba2897ed8eaa34ef97f24fda" +dependencies = [ + "bytes", + "futures", + "native-tls", + "tokio-dns-unofficial", + "tokio-io", + "tokio-tcp", + "tokio-tls", + "tungstenite", +] + +[[package]] name = "tokio-udp" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2289,6 +2404,26 @@ dependencies = [ ] [[package]] +name = "tungstenite" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577caf571708961603baf59d2e148d12931e0da2e4bb6c5b471dd4a524fef3aa" +dependencies = [ + "base64 0.10.1", + "byteorder", + "bytes", + "http", + "httparse", + "input_buffer", + "log", + "native-tls", + "rand 0.6.5", + "sha-1", + "url 2.1.0", + "utf-8", +] + +[[package]] name = "twoway" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2393,6 +2528,12 @@ dependencies = [ ] [[package]] +name = "utf-8" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" + +[[package]] name = "utf8parse" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -12,6 +12,7 @@ categories = ["command-line-utilities"] license = "MIT" [dependencies] +base64 = "0.11" bytes = "0.4" clap = { version = "2", features = ["wrap_help"] } component-future = "0.1" @@ -20,7 +21,9 @@ crossterm = { version = "0.13", features = ["terminal", "input", "screen"], defa directories = "2" env_logger = "0.7" futures = "0.1.29" -gotham = "0.4" +# for websocket support - should be able to go back to released version in 0.5 +gotham = { git = "https://github.com/gotham-rs/gotham", rev = "d2395926b93710832f8d72b49c9bd3e77516e386" } +hyper = "0.12" lazy_static = "1" log = { version = "0.4", features = ["release_max_level_info"] } mio = "0.6.19" @@ -32,12 +35,14 @@ ratelimit_meter = "5" regex = { version = "1", features = ["std", "perf"], default_features = false } reqwest = "0.9.22" serde = "1" +sha1 = "0.6" snafu = { version = "0.5", features = ["rust_1_30", "futures-01"], default_features = false } tokio = { version = "0.1.22", features = ["codec", "fs", "io", "reactor", "rt-full", "sync", "tcp", "timer"], default_features = false } tokio-pty-process-stream = "0.2" tokio-signal = "0.2" tokio-terminal-resize = "0.1" tokio-tls = "0.2" +tokio-tungstenite = "0.9" ttyrec = "0.2" twoway = "0.2" url = "2" @@ -1,11 +1,87 @@ +mod ws; + +use futures::{Future as _, Sink as _, Stream as _}; use gotham::router::builder::{DefineSingleRoute as _, DrawRoutes as _}; +use gotham::state::FromState as _; -pub fn router() -> gotham::router::Router { +pub fn router() -> impl gotham::handler::NewHandler { gotham::router::builder::build_simple_router(|route| { route.get("/").to(root); + route.get("/ws").to(handle_websocket_connection); }) } fn root(state: gotham::state::State) -> (gotham::state::State, String) { + log::info!("request for /"); (state, "hello world".to_string()) } + +fn handle_websocket_connection( + mut state: gotham::state::State, +) -> (gotham::state::State, hyper::Response<hyper::Body>) { + let body = hyper::Body::take_from(&mut state); + let headers = hyper::HeaderMap::take_from(&mut state); + if ws::requested(&headers) { + let (response, stream) = match ws::accept(&headers, body) { + Ok(res) => res, + Err(_) => { + log::error!("failed to accept websocket request"); + return ( + state, + hyper::Response::builder() + .status(hyper::StatusCode::BAD_REQUEST) + .body(hyper::Body::empty()) + .unwrap(), + ); + } + }; + let req_id = gotham::state::request_id(&state).to_owned(); + let stream = stream + .map_err(|e| { + log::error!( + "error upgrading connection for websockets: {}", + e + ) + }) + .and_then(move |stream| handle_websocket_stream(req_id, stream)); + tokio::spawn(stream); + (state, response) + } else { + ( + state, + hyper::Response::new(hyper::Body::from( + "non-websocket request to websocket endpoint", + )), + ) + } +} + +fn handle_websocket_stream<S>( + req_id: String, + stream: S, +) -> impl futures::Future<Item = (), Error = ()> +where + S: futures::Stream< + Item = tokio_tungstenite::tungstenite::protocol::Message, + Error = tokio_tungstenite::tungstenite::Error, + > + futures::Sink< + SinkItem = tokio_tungstenite::tungstenite::protocol::Message, + SinkError = tokio_tungstenite::tungstenite::Error, + >, +{ + let (sink, stream) = stream.split(); + sink.send_all(stream.map(move |msg| { + handle_websocket_message(&req_id, &msg); + msg + })) + .map_err(|e| log::error!("error during websocket stream: {}", e)) + .map(|_| log::info!("disconnect")) +} + +fn handle_websocket_message( + req_id: &str, + msg: &tokio_tungstenite::tungstenite::protocol::Message, +) { + // TODO + log::info!("websocket stream message for {}: {:?}", req_id, msg); +} diff --git a/src/web/ws.rs b/src/web/ws.rs new file mode 100644 index 0000000..ad47147 --- /dev/null +++ b/src/web/ws.rs @@ -0,0 +1,61 @@ +// from https://github.com/gotham-rs/gotham/blob/master/examples/websocket/src/main.rs + +use futures::future::Future as _; + +const PROTO_WEBSOCKET: &str = "websocket"; +const SEC_WEBSOCKET_KEY: &str = "Sec-WebSocket-Key"; +const SEC_WEBSOCKET_ACCEPT: &str = "Sec-WebSocket-Accept"; + +pub fn requested(headers: &hyper::HeaderMap) -> bool { + headers.get(hyper::header::UPGRADE) + == Some(&hyper::header::HeaderValue::from_static(PROTO_WEBSOCKET)) +} + +pub fn accept( + headers: &hyper::HeaderMap, + body: hyper::Body, +) -> Result< + ( + hyper::Response<hyper::Body>, + impl futures::Future< + Item = tokio_tungstenite::WebSocketStream< + hyper::upgrade::Upgraded, + >, + Error = hyper::Error, + >, + ), + (), +> { + let res = response(headers)?; + let ws = body.on_upgrade().map(|upgraded| { + tokio_tungstenite::WebSocketStream::from_raw_socket( + upgraded, + tokio_tungstenite::tungstenite::protocol::Role::Server, + None, + ) + }); + + Ok((res, ws)) +} + +fn response( + headers: &hyper::HeaderMap, +) -> Result<hyper::Response<hyper::Body>, ()> { + let key = headers.get(SEC_WEBSOCKET_KEY).ok_or(())?; + + Ok(hyper::Response::builder() + .header(hyper::header::UPGRADE, PROTO_WEBSOCKET) + .header(hyper::header::CONNECTION, "upgrade") + .header(SEC_WEBSOCKET_ACCEPT, accept_key(key.as_bytes())) + .status(hyper::StatusCode::SWITCHING_PROTOCOLS) + .body(hyper::Body::empty()) + .unwrap()) +} + +fn accept_key(key: &[u8]) -> String { + const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + let mut sha1 = sha1::Sha1::default(); + sha1.update(key); + sha1.update(WS_GUID); + base64::encode(&sha1.digest().bytes()) +} |