1
0
mirror of https://github.com/actix/examples synced 2025-02-20 08:20:32 +01:00

Merge pull request #42 from botika/master

fix missing websocket heartbeat and python client in websocket-tcp-chat
This commit is contained in:
Douman 2018-08-28 16:03:12 +03:00 committed by GitHub
commit d653fe4822
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 21 deletions

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""websocket cmd client for wssrv.py example.""" """websocket cmd client for actix/websocket-tcp-chat example."""
import argparse import argparse
import asyncio import asyncio
import signal import signal
@ -7,46 +7,54 @@ import sys
import aiohttp import aiohttp
queue = asyncio.Queue()
def start_client(loop, url):
async def start_client(url, loop):
name = input('Please enter your name: ') name = input('Please enter your name: ')
# send request ws = await aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False)
ws = yield from aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False)
# input reader
def stdin_callback(): def stdin_callback():
line = sys.stdin.buffer.readline().decode('utf-8') line = sys.stdin.buffer.readline().decode('utf-8')
if not line: if not line:
loop.stop() loop.stop()
else: else:
ws.send_str(name + ': ' + line) # Queue.put is a coroutine, so you can't call it directly.
loop.add_reader(sys.stdin.fileno(), stdin_callback) asyncio.ensure_future(queue.put(ws.send_str(name + ': ' + line)))
@asyncio.coroutine loop.add_reader(sys.stdin, stdin_callback)
def dispatch():
async def dispatch():
while True: while True:
msg = yield from ws.receive() msg = await ws.receive()
if msg.type == aiohttp.WSMsgType.TEXT: if msg.type == aiohttp.WSMsgType.TEXT:
print('Text: ', msg.data.strip()) print('Text: ', msg.data.strip())
elif msg.type == aiohttp.WSMsgType.BINARY: elif msg.type == aiohttp.WSMsgType.BINARY:
print('Binary: ', msg.data) print('Binary: ', msg.data)
elif msg.type == aiohttp.WSMsgType.PING: elif msg.type == aiohttp.WSMsgType.PING:
ws.pong() await ws.pong()
elif msg.type == aiohttp.WSMsgType.PONG: elif msg.type == aiohttp.WSMsgType.PONG:
print('Pong received') print('Pong received')
else: else:
if msg.type == aiohttp.WSMsgType.CLOSE: if msg.type == aiohttp.WSMsgType.CLOSE:
yield from ws.close() await ws.close()
elif msg.type == aiohttp.WSMsgType.ERROR: elif msg.type == aiohttp.WSMsgType.ERROR:
print('Error during receive %s' % ws.exception()) print('Error during receive %s' % ws.exception())
elif msg.type == aiohttp.WSMsgType.CLOSED: elif msg.type == aiohttp.WSMsgType.CLOSED:
pass pass
break break
yield from dispatch() await dispatch()
async def tick():
while True:
await (await queue.get())
async def main(url, loop):
await asyncio.wait([start_client(url, loop), tick()])
ARGS = argparse.ArgumentParser( ARGS = argparse.ArgumentParser(
@ -68,5 +76,5 @@ if __name__ == '__main__':
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, loop.stop) loop.add_signal_handler(signal.SIGINT, loop.stop)
asyncio.Task(start_client(loop, url)) asyncio.Task(main(url, loop))
loop.run_forever() loop.run_forever()

View File

@ -21,6 +21,7 @@ use std::time::Instant;
use actix::*; use actix::*;
use actix_web::server::HttpServer; use actix_web::server::HttpServer;
use actix_web::{fs, http, ws, App, Error, HttpRequest, HttpResponse}; use actix_web::{fs, http, ws, App, Error, HttpRequest, HttpResponse};
use std::time::Duration;
mod codec; mod codec;
mod server; mod server;
@ -68,6 +69,10 @@ impl Actor for WsChatSession {
// before processing any other events. // before processing any other events.
// HttpContext::state() is instance of WsChatSessionState, state is shared // HttpContext::state() is instance of WsChatSessionState, state is shared
// across all routes within application // across all routes within application
// we'll start heartbeat process on session start.
self.hb(ctx);
let addr = ctx.address(); let addr = ctx.address();
ctx.state() ctx.state()
.addr .addr
@ -183,6 +188,31 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
} }
} }
impl WsChatSession {
/// helper method that sends ping to client every second.
///
/// also this method check heartbeats from client
fn hb(&self, ctx: &mut ws::WebsocketContext<Self, WsChatSessionState>) {
ctx.run_interval(Duration::new(1, 0), |act, ctx| {
// check client heartbeats
if Instant::now().duration_since(act.hb) > Duration::new(10, 0) {
// heartbeat timed out
println!("Websocket Client heartbeat failed, disconnecting!");
// notify chat server
ctx.state()
.addr
.do_send(server::Disconnect { id: act.id });
// stop actor
ctx.stop();
}
ctx.ping("");
});
}
}
fn main() { fn main() {
let _ = env_logger::init(); let _ = env_logger::init();
let sys = actix::System::new("websocket-example"); let sys = actix::System::new("websocket-example");

View File

@ -149,7 +149,7 @@ impl ChatSession {
/// ///
/// also this method check heartbeats from client /// also this method check heartbeats from client
fn hb(&self, ctx: &mut Context<Self>) { fn hb(&self, ctx: &mut Context<Self>) {
ctx.run_later(Duration::new(1, 0), |act, ctx| { ctx.run_interval(Duration::new(1, 0), |act, ctx| {
// check client heartbeats // check client heartbeats
if Instant::now().duration_since(act.hb) > Duration::new(10, 0) { if Instant::now().duration_since(act.hb) > Duration::new(10, 0) {
// heartbeat timed out // heartbeat timed out
@ -164,7 +164,6 @@ impl ChatSession {
act.framed.write(ChatResponse::Ping); act.framed.write(ChatResponse::Ping);
// if we can not send message to sink, sink is closed (disconnected) // if we can not send message to sink, sink is closed (disconnected)
act.hb(ctx);
}); });
} }
} }