Solstice client.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

201 lines
5.4 KiB

use std::error;
use std::fmt;
use crossbeam_channel;
use rustc_serialize::json;
use solstice_proto::config;
use ws;
use super::request::*;
use super::response::*;
/// This enum contains the possible notifications that the control loop will
/// send to the client.
#[derive(Debug)]
pub enum Notification {
/// A new controller has connected: control messages can now be sent on the
/// given channel.
Connected(Sender),
/// The controller has disconnected.
Disconnected,
/// An irretrievable error has arisen.
Error(String),
/// The controller has sent a request.
Request(Request),
}
/// This error is returned when a `Sender` fails to send a control request.
#[derive(Debug)]
pub enum SendError {
/// Error encoding the control request.
JSONEncoderError(json::EncoderError),
/// Error sending the encoded control request to the websocket.
WebSocketError(ws::Error),
}
impl fmt::Display for SendError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
SendError::JSONEncoderError(ref err) => {
write!(fmt, "JSONEncoderError: {}", err)
}
SendError::WebSocketError(ref err) => {
write!(fmt, "WebSocketError: {}", err)
}
}
}
}
impl error::Error for SendError {
fn description(&self) -> &str {
match *self {
SendError::JSONEncoderError(_) => "JSONEncoderError",
SendError::WebSocketError(_) => "WebSocketError",
}
}
fn cause(&self) -> Option<&dyn error::Error> {
match *self {
SendError::JSONEncoderError(ref err) => Some(err),
SendError::WebSocketError(ref err) => Some(err),
}
}
}
impl From<json::EncoderError> for SendError {
fn from(err: json::EncoderError) -> Self {
SendError::JSONEncoderError(err)
}
}
impl From<ws::Error> for SendError {
fn from(err: ws::Error) -> Self {
SendError::WebSocketError(err)
}
}
/// This struct is used to send control responses to the controller.
/// It encapsulates the websocket connection so as to isolate clients from
/// the underlying implementation.
#[derive(Clone, Debug)]
pub struct Sender {
sender: ws::Sender,
}
impl Sender {
/// Queues up a control response to be sent to the controller.
pub fn send(&mut self, response: Response) -> Result<(), SendError> {
let encoded = json::encode(&response)?;
self.sender.send(encoded)?;
Ok(())
}
}
/// This struct handles a single websocket connection.
#[derive(Debug)]
struct Handler {
/// The channel on which to send notifications to the client.
client_tx: crossbeam_channel::Sender<Notification>,
/// The channel on which to send messages to the controller.
socket_tx: ws::Sender,
}
impl Handler {
fn send_to_client(&self, notification: Notification) -> ws::Result<()> {
match self.client_tx.send(notification) {
Ok(()) => Ok(()),
Err(e) => {
error!("Error sending notification to client: {}", e);
Err(ws::Error::new(ws::ErrorKind::Internal, ""))
}
}
}
}
impl ws::Handler for Handler {
fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
info!("Websocket open");
self.send_to_client(Notification::Connected(Sender {
sender: self.socket_tx.clone(),
}))
}
fn on_close(&mut self, code: ws::CloseCode, reason: &str) {
info!("Websocket closed: code: {:?}, reason: {:?}", code, reason);
self
.send_to_client(Notification::Disconnected)
.unwrap_or(())
}
fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
// Get the payload string.
let payload = match msg {
ws::Message::Text(payload) => payload,
ws::Message::Binary(_) => {
error!("Received binary websocket message from controller");
return Err(ws::Error::new(
ws::ErrorKind::Protocol,
"Binary message not supported",
));
}
};
// Decode the json control request.
let control_request = match json::decode(&payload) {
Ok(control_request) => control_request,
Err(e) => {
error!("Received invalid JSON message from controller: {}", e);
return Err(ws::Error::new(ws::ErrorKind::Protocol, "Invalid JSON"));
}
};
debug!("Received control request: {:?}", control_request);
// Send the control request to the client.
self.send_to_client(Notification::Request(control_request))
}
}
/// Start listening on the socket address stored in configuration, and send
/// control notifications to the client through the given channel.
pub fn listen(client_tx: crossbeam_channel::Sender<Notification>) {
let websocket_result = ws::Builder::new()
.with_settings(ws::Settings {
max_connections: 1,
..ws::Settings::default()
})
.build(|socket_tx| Handler {
client_tx: client_tx.clone(),
socket_tx: socket_tx,
});
let websocket = match websocket_result {
Ok(websocket) => websocket,
Err(e) => {
error!("Unable to build websocket: {}", e);
client_tx
.send(Notification::Error(format!(
"Unable to build websocket: {}",
e
)))
.unwrap();
return;
}
};
let listen_result =
websocket.listen((config::CONTROL_HOST, config::CONTROL_PORT));
match listen_result {
Ok(_) => (),
Err(e) => {
error!("Unable to listen on websocket: {}", e);
client_tx
.send(Notification::Error(format!(
"Unable to listen on websocket: {}",
e
)))
.unwrap();
}
}
}