Skip to content

Commit

Permalink
Merge pull request #66 from snapview/fix-close
Browse files Browse the repository at this point in the history
Fix error `ConnectionClosed` never returned to server when server initiated close
  • Loading branch information
Alexey Galakhov committed Aug 7, 2019
2 parents c7e1cf1 + e2bec4b commit b40256e
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 6 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ authors = ["Alexey Galakhov"]
license = "MIT/Apache-2.0"
readme = "README.md"
homepage = "https://github.com/snapview/tungstenite-rs"
documentation = "https://docs.rs/tungstenite/0.9.0"
documentation = "https://docs.rs/tungstenite/0.9.1"
repository = "https://github.com/snapview/tungstenite-rs"
version = "0.9.0"
version = "0.9.1"

[features]
default = ["tls"]
Expand Down
12 changes: 8 additions & 4 deletions src/protocol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use self::frame::coding::{OpCode, Data as OpData, Control as OpCtl, CloseCode};
use util::NonBlockingResult;

/// Indicates a Client or Server role of the websocket
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Role {
/// This socket is a server
Server,
Expand Down Expand Up @@ -295,8 +295,13 @@ impl WebSocketContext {
// If we get to this point, the send queue is empty and the underlying socket is still
// willing to take more data.

let closing_state = match self.state {
WebSocketState::ClosedByPeer | WebSocketState::CloseAcknowledged => true,
_ => false,
};

// If we're closing and there is nothing to send anymore, we should close the connection.
if let (Role::Server, WebSocketState::ClosedByPeer) = (&self.role, &self.state) {
if self.role == Role::Server && closing_state {
// The underlying TCP connection, in most normal cases, SHOULD be closed
// first by the server, so that it holds the TIME_WAIT state and not the
// client (as this would prevent it from re-opening the connection for 2
Expand Down Expand Up @@ -567,8 +572,7 @@ impl WebSocketState {
/// Check if the state is active, return error if not.
fn check_active(&self) -> Result<()> {
match self {
WebSocketState::Terminated
=> Err(Error::AlreadyClosed),
WebSocketState::Terminated => Err(Error::AlreadyClosed),
_ => Ok(()),
}
}
Expand Down
62 changes: 62 additions & 0 deletions tests/connection_reset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//! Verifies that the server returns a `ConnectionClosed` error when the connection
//! is closedd from the server's point of view and drop the underlying tcp socket.

extern crate env_logger;
extern crate tungstenite;
extern crate url;

use std::net::TcpListener;
use std::process::exit;
use std::thread::{spawn, sleep};
use std::time::Duration;

use tungstenite::{accept, connect, Error, Message};
use url::Url;

#[test]
fn test_close() {
env_logger::init();

spawn(|| {
sleep(Duration::from_secs(5));
println!("Unit test executed too long, perhaps stuck on WOULDBLOCK...");
exit(1);
});

let server = TcpListener::bind("127.0.0.1:3012").unwrap();

let client_thread = spawn(move || {
let (mut client, _) = connect(Url::parse("ws://localhost:3012/socket").unwrap()).unwrap();

client.write_message(Message::Text("Hello WebSocket".into())).unwrap();

let message = client.read_message().unwrap(); // receive close from server
assert!(message.is_close());

let err = client.read_message().unwrap_err(); // now we should get ConnectionClosed
match err {
Error::ConnectionClosed => { },
_ => panic!("unexpected error"),
}
});

let client_handler = server.incoming().next().unwrap();
let mut client_handler = accept(client_handler.unwrap()).unwrap();

let message = client_handler.read_message().unwrap();
assert_eq!(message.into_data(), b"Hello WebSocket");

client_handler.close(None).unwrap(); // send close to client

assert!(client_handler.read_message().unwrap().is_close()); // receive acknowledgement

let err = client_handler.read_message().unwrap_err(); // now we should get ConnectionClosed
match err {
Error::ConnectionClosed => { },
_ => panic!("unexpected error"),
}

drop(client_handler);

client_thread.join().unwrap();
}

0 comments on commit b40256e

Please sign in to comment.