Skip to content

Instantly share code, notes, and snippets.

@stefanotorresi
Last active November 5, 2021 07:43
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stefanotorresi/bfb9716f73f172ac4439436456a96d6f to your computer and use it in GitHub Desktop.
Save stefanotorresi/bfb9716f73f172ac4439436456a96d6f to your computer and use it in GitHub Desktop.
Playing with Python asyncio and websockets
<!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>
#!/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