Last active
November 5, 2021 07:43
-
-
Save stefanotorresi/bfb9716f73f172ac4439436456a96d6f to your computer and use it in GitHub Desktop.
Playing with Python asyncio and websockets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Chat Client</title> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> | |
<style type="text/css"> | |
#main-panel .tab-content { | |
margin-top: 10px; | |
height: 400px; | |
overflow-y: scroll; | |
} | |
</style> | |
</head> | |
<body> | |
<header class="jumbotron"> | |
<div class="container"> | |
<h2>Websocket Chat</h2> | |
</div> | |
</header> | |
<div class="container"> | |
<div class="row"> | |
<div class="col-xs-9"> | |
<div id="main-panel" class="panel panel-default"> | |
<div class="panel-body"> | |
<ul class="nav nav-tabs" role="tablist"> | |
<li role="presentation" class="active"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">Messages</a></li> | |
<li role="presentation"><a href="#statuses" aria-controls="statuses" role="tab" data-toggle="tab">Statuses</a></li> | |
</ul> | |
<div class="tab-content"> | |
<div class="tab-pane active" id="messages" role="tabpanel"></div> | |
<div class="tab-pane" id="statuses" role="tabpanel"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="col-xs-3"> | |
<div class="panel panel-default"> | |
<div class="panel-heading"> | |
<h3 class="panel-title">Users</h3> | |
</div> | |
<div id="users" class="panel-body"></div> | |
</div> | |
</div> | |
</div> | |
<form action="javascript" onsubmit="return send_message(this)"> | |
<input type="text" id="message_input" class="form-control" placeholder="Type a message..."> | |
</form> | |
</div> | |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> | |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> | |
<script type="text/javascript"> | |
var ws = new WebSocket("ws://127.0.0.1:1299/"); | |
messages = $("#messages"); | |
ws.onmessage = event => print_message(event.data) | |
ws.onopen = event => { | |
console.log(event) | |
print_status(event.type) | |
}; | |
ws.onclose = event => { | |
console.log(event) | |
print_status(event.type) | |
}; | |
ws.onerror = event => { | |
console.log(event) | |
print_status(event.type) | |
}; | |
function print_message(message) | |
{ | |
messages.append("<p>" + message + "</p>") | |
messages.scrollTop(messages[0].scrollHeight) | |
} | |
function send_message(form) | |
{ | |
ws.send(form.querySelector('#message_input').value); | |
return false; | |
} | |
function print_status(status) | |
{ | |
document.getElementById("statuses").innerHTML += "<p>" + status + "</p>" | |
} | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
websockets==3.2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import websockets | |
from websockets import WebSocketServerProtocol | |
import asyncio | |
import logging | |
class Server: | |
def __init__(self): | |
self.clients = set() | |
self.loop = asyncio.get_event_loop() | |
self.logger = _create_logger() | |
self._client_timeout = 5 | |
self._wake_up_task = None | |
def listen(self, host='localhost', port=1299): | |
self.logger.info("listening on {}:{}".format(host, port)) | |
ws_server = websockets.serve(self.connect_client, host, port) | |
self.loop.run_until_complete(ws_server) | |
self._wake_up_task = asyncio.ensure_future(_wake_up()) | |
try: | |
self.loop.run_forever() | |
except KeyboardInterrupt: | |
self.logger.debug('caught keyboard interrupt') | |
self.exit() | |
async def connect_client(self, client: WebSocketServerProtocol, path): | |
self.clients.add(client) | |
self.logger.info('new client connected from {}:{}'.format(*client.remote_address)) | |
keep_alive_task = asyncio.ensure_future(self.keep_alive(client)) | |
try: | |
await self.handle_messages(client) | |
except websockets.ConnectionClosed: | |
keep_alive_task.cancel() | |
await self.disconnect_client(client) | |
async def handle_messages(self, client): | |
while True: | |
message = await client.recv() | |
self.logger.info('recieved message from {}:{}: {}'.format(*client.remote_address, message)) | |
await asyncio.wait([client.send(message) for client in self.clients]) | |
async def disconnect_client(self, client): | |
await client.close() | |
self.clients.remove(client) | |
self.logger.info('client {}:{} disconnected'.format(*client.remote_address)) | |
async def keep_alive(self, client: WebSocketServerProtocol): | |
while True: | |
await asyncio.sleep(self._client_timeout) | |
try: | |
self.logger.info('pinging {}:{}'.format(*client.remote_address)) | |
await asyncio.wait_for(client.ping(), self._client_timeout) | |
except asyncio.TimeoutError: | |
self.logger.info('client {}:{} timed out'.format(*client.remote_address)) | |
await self.disconnect_client(client) | |
def exit(self): | |
self.logger.info("exiting") | |
self._wake_up_task.cancel() | |
try: | |
self.loop.run_until_complete(self._wake_up_task) | |
except asyncio.CancelledError: | |
self.loop.close() | |
def _create_logger(): | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger("chat.server") | |
logger.setLevel(logging.INFO) | |
ws_logger = logging.getLogger('websockets.server') | |
ws_logger.setLevel(logging.ERROR) | |
ws_logger.addHandler(logging.StreamHandler()) | |
return logger | |
async def _wake_up(): | |
while True: | |
await asyncio.sleep(1) | |
def main(): | |
server = Server() | |
server.listen() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment