1
0
mirror of https://github.com/actix/examples synced 2025-06-26 17:17:42 +02:00

update all websocket examples to v4

This commit is contained in:
Rob Ede
2022-02-18 01:44:53 +00:00
parent 1b23e3ff3d
commit 4d8573c3fe
40 changed files with 1340 additions and 1682 deletions

View File

@ -0,0 +1,27 @@
[package]
name = "websocket"
version = "1.0.0"
edition = "2021"
[[bin]]
name = "websocket-server"
path = "src/main.rs"
[[bin]]
name = "websocket-client"
path = "src/client.rs"
[dependencies]
actix = "0.12"
actix-codec = "0.5"
actix-files = "0.6.0-beta.16"
actix-rt = "2"
actix-web = "4.0.0-rc.3"
actix-web-actors = "4.0.0-beta.12"
awc = "3.0.0-beta.21"
env_logger = "0.9"
log = "0.4"
futures = "0.3.7"
tokio = { version = "1.13.1", features = ["full"] }
tokio-stream = "0.1.8"

34
websockets/echo/README.md Normal file
View File

@ -0,0 +1,34 @@
# websocket
Simple echo websocket server.
## Usage
### server
```bash
cd websockets/websocket
cargo run --bin websocket-server
# Started http server: 127.0.0.1:8080
```
### web client
- [http://localhost:8080/index.html](http://localhost:8080/index.html)
### rust client
```bash
cd websockets/websocket
cargo run --bin websocket-client
```
### python client
- ``pip install aiohttp``
- ``python websocket-client.py``
if ubuntu :
- ``pip3 install aiohttp``
- ``python3 websocket-client.py``

View File

@ -0,0 +1,72 @@
//! Simple websocket client.
use std::{io, thread};
use actix_web::web::Bytes;
use awc::ws;
use futures::{SinkExt as _, StreamExt as _};
use tokio::{select, sync::mpsc};
use tokio_stream::wrappers::UnboundedReceiverStream;
#[actix_web::main]
async fn main() {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
log::info!("starting echo WebSocket client");
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
let mut cmd_rx = UnboundedReceiverStream::new(cmd_rx);
// run blocking terminal input reader on separate thread
let input_thread = thread::spawn(move || loop {
let mut cmd = String::with_capacity(32);
if io::stdin().read_line(&mut cmd).is_err() {
log::error!("error reading line");
return;
}
cmd_tx.send(cmd).unwrap();
});
let (res, mut ws) = awc::Client::new()
.ws("ws://127.0.0.1:8080/ws")
.connect()
.await
.unwrap();
log::debug!("response: {res:?}");
log::info!("connected; server will echo messages sent");
loop {
select! {
Some(msg) = ws.next() => {
match msg {
Ok(ws::Frame::Text(txt)) => {
// log echoed messages from server
log::info!("Server: {:?}", txt)
}
Ok(ws::Frame::Ping(_)) => {
// respond to ping probes
ws.send(ws::Message::Pong(Bytes::new())).await.unwrap();
}
_ => {}
}
}
Some(cmd) = cmd_rx.next() => {
if cmd.is_empty() {
continue;
}
ws.send(ws::Message::Text(cmd.into())).await.unwrap();
}
else => break
}
}
input_thread.join().unwrap();
}

View File

@ -0,0 +1,42 @@
//! Simple echo websocket server.
//!
//! Open `http://localhost:8080/` in browser to test.
use actix_files::NamedFile;
use actix_web::{
middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, Responder,
};
use actix_web_actors::ws;
mod server;
use self::server::MyWebSocket;
async fn index() -> impl Responder {
NamedFile::open_async("./static/index.html").await.unwrap()
}
/// WebSocket handshake and start `MyWebSocket` actor.
async fn echo_ws(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
ws::start(MyWebSocket::new(), &req, stream)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
log::info!("starting HTTP server at http://localhost:8080");
HttpServer::new(|| {
App::new()
// WebSocket UI HTML file
.service(web::resource("/").to(index))
// websocket route
.service(web::resource("/ws").route(web::get().to(echo_ws)))
// enable logger
.wrap(middleware::Logger::default())
})
.workers(2)
.bind(("127.0.0.1", 8080))?
.run()
.await
}

View File

@ -0,0 +1,82 @@
use std::time::{Duration, Instant};
use actix::prelude::*;
use actix_web_actors::ws;
/// How often heartbeat pings are sent
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
/// How long before lack of client response causes a timeout
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
/// websocket connection is long running connection, it easier
/// to handle with an actor
pub struct MyWebSocket {
/// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
/// otherwise we drop connection.
hb: Instant,
}
impl MyWebSocket {
pub fn new() -> Self {
Self { hb: Instant::now() }
}
/// helper method that sends ping to client every second.
///
/// also this method checks heartbeats from client
fn hb(&self, ctx: &mut <Self as Actor>::Context) {
ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
// check client heartbeats
if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
// heartbeat timed out
println!("Websocket Client heartbeat failed, disconnecting!");
// stop actor
ctx.stop();
// don't try to send a ping
return;
}
ctx.ping(b"");
});
}
}
impl Actor for MyWebSocket {
type Context = ws::WebsocketContext<Self>;
/// Method is called on actor start. We start the heartbeat process here.
fn started(&mut self, ctx: &mut Self::Context) {
self.hb(ctx);
}
}
/// Handler for `ws::Message`
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWebSocket {
fn handle(
&mut self,
msg: Result<ws::Message, ws::ProtocolError>,
ctx: &mut Self::Context,
) {
// process websocket messages
println!("WS: {:?}", msg);
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(),
}
}
}

View File

@ -0,0 +1,171 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Websocket Echo</title>
<style>
:root {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 18px;
}
input[type='text'] {
font-size: inherit;
}
#log {
width: 30em;
height: 20em;
overflow: auto;
margin: 0.5em 0;
border: 1px solid black;
}
#status {
padding: 0 0.2em;
}
#text {
width: 17em;
padding: 0.5em;
}
.msg {
margin: 0;
padding: 0.25em 0.5em;
}
.msg--status {
/* a light yellow */
background-color: #ffffc9;
}
.msg--message {
/* a light blue */
background-color: #d2f4ff;
}
.msg--error {
background-color: pink;
}
</style>
</head>
<body>
<h1>Chat!</h1>
<div>
<button id="connect">Connect</button>
<span>Status:</span>
<span id="status">disconnected</span>
</div>
<div id="log"></div>
<form id="chatform">
<input type="text" id="text" />
<input type="submit" id="send" />
</form>
<hr />
<section>
<h2>Usage</h2>
<p>After connecting, type into the text box and the server will echo your message.</p>
</section>
<script>
const $status = document.querySelector('#status')
const $connectButton = document.querySelector('#connect')
const $log = document.querySelector('#log')
const $form = document.querySelector('#chatform')
const $input = document.querySelector('#text')
/** @type {WebSocket | null} */
var socket = null
function log(msg, type = 'status') {
$log.innerHTML += `<p class="msg msg--${type}">${msg}</p>`
$log.scrollTop += 1000
}
function connect() {
disconnect()
const { location } = window
const proto = location.protocol.startsWith('https') ? 'wss' : 'ws'
const wsUri = `${proto}://${location.host}/ws`
log('Connecting...')
socket = new WebSocket(wsUri)
socket.onopen = () => {
log('Connected')
updateConnectionStatus()
}
socket.onmessage = (ev) => {
log('Received: ' + ev.data, 'message')
}
socket.onclose = () => {
log('Disconnected')
socket = null
updateConnectionStatus()
}
}
function disconnect() {
if (socket) {
log('Disconnecting...')
socket.close()
socket = null
updateConnectionStatus()
}
}
function updateConnectionStatus() {
if (socket) {
$status.style.backgroundColor = 'transparent'
$status.style.color = 'green'
$status.textContent = `connected`
$connectButton.innerHTML = 'Disconnect'
$input.focus()
} else {
$status.style.backgroundColor = 'red'
$status.style.color = 'white'
$status.textContent = 'disconnected'
$connectButton.textContent = 'Connect'
}
}
$connectButton.addEventListener('click', () => {
if (socket) {
disconnect()
} else {
connect()
}
updateConnectionStatus()
})
$form.addEventListener('submit', (ev) => {
ev.preventDefault()
const text = $input.value
log('Sending: ' + text)
socket.send(text)
$input.value = ''
$input.focus()
})
updateConnectionStatus()
</script>
</body>
</html>

View File

@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""websocket cmd client for wssrv.py example."""
import argparse
import asyncio
import signal
import sys
import aiohttp
def start_client(loop, url):
name = input('Please enter your name: ')
# send request
ws = yield from aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False)
# input reader
def stdin_callback():
line = sys.stdin.buffer.readline().decode('utf-8')
if not line:
loop.stop()
else:
ws.send_str(name + ': ' + line)
loop.add_reader(sys.stdin.fileno(), stdin_callback)
@asyncio.coroutine
def dispatch():
while True:
msg = yield from ws.receive()
if msg.type == aiohttp.WSMsgType.TEXT:
print('Text: ', msg.data.strip())
elif msg.type == aiohttp.WSMsgType.BINARY:
print('Binary: ', msg.data)
elif msg.type == aiohttp.WSMsgType.PING:
ws.pong()
elif msg.type == aiohttp.WSMsgType.PONG:
print('Pong received')
else:
if msg.type == aiohttp.WSMsgType.CLOSE:
yield from ws.close()
elif msg.type == aiohttp.WSMsgType.ERROR:
print('Error during receive %s' % ws.exception())
elif msg.type == aiohttp.WSMsgType.CLOSED:
pass
break
yield from dispatch()
ARGS = argparse.ArgumentParser(
description="websocket console client for wssrv.py example.")
ARGS.add_argument(
'--host', action="store", dest='host',
default='127.0.0.1', help='Host name')
ARGS.add_argument(
'--port', action="store", dest='port',
default=8080, type=int, help='Port number')
if __name__ == '__main__':
args = ARGS.parse_args()
if ':' in args.host:
args.host, port = args.host.split(':', 1)
args.port = int(port)
url = 'http://{}:{}/ws/'.format(args.host, args.port)
loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, loop.stop)
asyncio.Task(start_client(loop, url))
loop.run_forever()