如果您正在学习 Web 套接字,您可能会编写一个聊天服务器,但今天让我们做一些不同的事情。这是我们今天要介绍的内容:学习如何编写 Web 套接字服务器以提高生产力。
创建 websocket 服务器:
- 用于呈现 html 的索引端点。
- 用于发送事件文件更改的 Websocket 端点。
- 用于监听来自 websocket 的事件文件更改的 Javascript。
Actix Web
在 Rust 世界中,actix web 具有丰富的构建 Web 应用程序的功能。所以让我们将这些库添加到我们的项目中。
actix = "0.13" actix-web = "4" actix-web-actors = "4.1"
|
接下来让我们创建用于在索引页面呈现 html 的处理程序。
接下来,让我们创建处理程序,在索引页上渲染html。
在这种情况下,我们将读取index.html文件作为html模板,并替换table.html文件中的内容。
#[get("/")] async fn index() -> Result<HttpResponse> { let content = std::fs::read_to_string("table.html").expect("table not found"); let body = include_str!("index.html") .to_string() .replace("{content}", &content); Ok(HttpResponse::build(StatusCode::OK) .content_type("text/html; charset=utf-8") .body(body)) }
|
并为我们的websocket服务器添加入口点:
use websocket::FileWatcherWebsocket;
async fn echo_ws(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> { ws::start(FileWatcherWebsocket::new(), &req, stream) }
|
然后是websocket服务器本身:
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(1); const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); const FILE_PATH: &'static str = "table.html";
pub struct FileWatcherWebsocket { hb: Instant, modified: SystemTime, }
impl FileWatcherWebsocket { pub fn new() -> Self { let metadata = fs::metadata(FILE_PATH).unwrap(); let modified = metadata.modified().unwrap();
Self { hb: Instant::now(), modified, } }
fn hb(&self, ctx: &mut <Self as Actor>::Context) { ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { println!("Websocket Client heartbeat failed, disconnecting!");
ctx.stop();
return; }
let modified = fs::metadata(FILE_PATH).unwrap().modified().unwrap(); if modified.duration_since(act.modified).unwrap() > Duration::from_millis(0) { act.modified = modified; println!("Sending file changes event! {}", &FILE_PATH); ctx.text("file_changed") }
ctx.ping(b""); }); } }
impl Actor for FileWatcherWebsocket { type Context = ws::WebsocketContext<Self>;
fn started(&mut self, ctx: &mut Self::Context) { self.hb(ctx); } }
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for FileWatcherWebsocket { fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) { match msg { Ok(ws::Message::Ping(msg)) => { self.hb = Instant::now(); ctx.pong(&msg); } Ok(ws::Message::Pong(_)) => { self.hb = Instant::now(); } Ok(ws::Message::Text(text)) => ctx.text(text), Ok(ws::Message::Binary(bin)) => ctx.binary(bin), Ok(ws::Message::Close(reason)) => { ctx.close(reason); ctx.stop(); } _ => ctx.stop(), } } }
|
这个websocket服务器将每隔一秒Ping一次客户端,然后检查最后修改的文件table.html,如果有任何文件变化,它将发送*file_changed事件给客户端。
客户端
幸运的是,如今支持websocket客户端开箱即用。因此,这里是我们在index.html文件中包含的客户端的代码。
<script> let socket = new WebSocket("ws://localhost:8080/ws");
socket.onopen = function(e) { console.log("[open] Connection established"); console.log("Sending to server"); socket.send("start_connection"); };
socket.onmessage = function(event) { console.log(`[message] Data received from server: ${event.data}`); if (event.data == "file_changed") { window.location.reload(); } };
socket.onclose = function(event) { if (event.wasClean) { console.log(`[close] Connection closed, code=${event.code} reason=${event.reason}`); } else { console.log('[close] Connection died'); } };
socket.onerror = function(error) { console.log(error) };
</script>
|
客户端将监听每个传入的消息,然后在有任何file_changed事件时重新加载浏览器。
这是一个非常简单的实现,但为我节省了大量的时间。
结论
我认为每当我们学习新技术时,用它来解决我们的问题会非常有趣,在这种情况下,我用它来自动重新加载网页,这让我减少了调试工作,我对此感到非常高兴。
如果你想查看完整的源代码,你可以看这里。