use std::{
cell::{Cell, RefCell},
net::{Ipv4Addr, SocketAddr},
rc::Rc,
time::{Duration, Instant},
};
use nexora::{
Error, Headers, HttpServer, HttpService, Proactor, Request, RequestContext, Responder,
Response, ServerConfig, StatusCode, SubsystemId, WebSocketClient, WebSocketMessage,
WebSocketServerSession, WebSocketSessionId, WebSocketUpgradeHandler,
websocket_client::{WebSocketClientConfig, WebSocketEvent},
};
struct IgnoreHttp;
impl HttpService for IgnoreHttp {
fn call(
&mut self,
_ctx: &mut RequestContext,
_req: Request,
responder: Responder,
) -> nexora::Result<()> {
responder.respond(Response::text(StatusCode::NOT_FOUND, "not found"))
}
}
struct EchoPush {
server_id: Rc<Cell<Option<WebSocketSessionId>>>,
}
impl WebSocketUpgradeHandler for EchoPush {
fn on_open(&mut self, session: &mut WebSocketServerSession) -> nexora::Result<()> {
self.server_id.set(Some(session.id()));
Ok(())
}
fn on_message(
&mut self,
session: &mut WebSocketServerSession,
message: WebSocketMessage,
) -> nexora::Result<()> {
match message {
WebSocketMessage::Text(_) | WebSocketMessage::Binary(_) => session.send(message),
WebSocketMessage::Ping(_) | WebSocketMessage::Pong(_) | WebSocketMessage::Close(_) => {
Ok(())
}
}
}
}
fn main() -> nexora::Result<()> {
let mut proactor = Proactor::builder().build()?;
let server_id = Rc::new(Cell::new(None));
let server_config = ServerConfig {
net_subsystem: SubsystemId::new_unchecked(3),
..ServerConfig::default()
};
let mut server = HttpServer::bind(
&mut proactor,
SocketAddr::from((Ipv4Addr::LOCALHOST, 0)),
server_config,
IgnoreHttp,
)?
.with_websocket_handler(
"/ws",
EchoPush {
server_id: Rc::clone(&server_id),
},
);
let port = server.local_addr().port();
let websocket_config = WebSocketClientConfig {
dns_subsystem: SubsystemId::new_unchecked(1),
net_subsystem: SubsystemId::new_unchecked(2),
dns_servers_csv: Some(String::from("127.0.0.1")),
connect_timeout: Duration::from_secs(2),
..WebSocketClientConfig::default()
};
let mut client = WebSocketClient::new(&mut proactor, websocket_config)?;
let opened = Rc::new(Cell::new(false));
let opened_flag = Rc::clone(&opened);
let open_error = Rc::new(RefCell::new(None));
let open_slot = Rc::clone(&open_error);
let url = format!("ws://127.0.0.1:{port}/ws");
let session = client.connect(
&mut proactor,
&url,
Headers::new(),
move |_ctx, _id, result| {
opened_flag.set(result.is_ok());
if let Err(error) = result {
*open_slot.borrow_mut() = Some(error.to_string());
}
},
)?;
let texts = Rc::new(RefCell::new(Vec::new()));
let inbox = Rc::clone(&texts);
client.set_message_handler(session, move |_ctx, _session, event| {
if let WebSocketEvent::Text(text) = event {
inbox.borrow_mut().push(text);
}
})?;
if let Err(error) = pump(&mut proactor, &mut server, &mut client, |client| {
opened.get() && client.is_open(session)
}) {
if let Some(detail) = open_error.borrow().clone() {
return Err(Error::InvalidState(detail));
}
return Err(error);
}
client.send_text(&mut proactor, session, "hello")?;
pump(&mut proactor, &mut server, &mut client, |_client| {
texts.borrow().iter().any(|text| text == "hello")
})?;
let fire_at = Instant::now() + Duration::from_millis(30);
while Instant::now() < fire_at {
server.drive(&mut proactor)?;
client.drive_with(&mut proactor, &mut server)?;
}
let Some(id) = server_id.get() else {
return Err(Error::InvalidState(String::from(
"websocket upgrade did not open a server session",
)));
};
server.websocket_send(id, WebSocketMessage::Text(String::from("tick")))?;
pump(&mut proactor, &mut server, &mut client, |_client| {
texts.borrow().iter().any(|text| text == "tick")
})?;
println!("websocket echo and push ok port={port}");
Ok(())
}
fn pump(
proactor: &mut Proactor,
server: &mut HttpServer<IgnoreHttp>,
client: &mut WebSocketClient,
ready: impl Fn(&WebSocketClient) -> bool,
) -> nexora::Result<()> {
let deadline = Instant::now() + Duration::from_secs(3);
while Instant::now() < deadline {
server.drive(proactor)?;
client.drive_with(proactor, &mut *server)?;
if ready(client) {
return Ok(());
}
}
Err(Error::Timeout)
}