Skip to content

Instantly share code, notes, and snippets.

@artizirk
Last active September 1, 2022 17:12
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save artizirk/04eb23d957d7916c01ca632bb27d5436 to your computer and use it in GitHub Desktop.
Save artizirk/04eb23d957d7916c01ca632bb27d5436 to your computer and use it in GitHub Desktop.
Python asyncio websockets http static file server, aka http and websocket server on the same port: python-websockets/websockets#116
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebSocket demo</title>
</head>
<body>
<script>
var ws = new WebSocket("ws://127.0.0.1:8765/"),
messages = document.createElement('ul');
ws.onmessage = function (event) {
var messages = document.getElementsByTagName('ul')[0],
message = document.createElement('li'),
content = document.createTextNode(event.data);
message.appendChild(content);
messages.appendChild(message);
};
document.body.appendChild(messages);
</script>
</body>
</html>
#!/usr/bin/env python3
"""
This code allows you to serve static files from the same port as the websocket connection
This is only suitable for small files and as a development server!
open(full_path, 'rb').read() call that is used to send files will block the whole asyncio loop!
"""
import os
import asyncio
import datetime
import random
import functools
import websockets
from http import HTTPStatus
MIME_TYPES = {
"html": "text/html",
"js": "text/javascript",
"css": "text/css"
}
async def process_request(sever_root, path, request_headers):
"""Serves a file when doing a GET request with a valid path."""
if "Upgrade" in request_headers:
return # Probably a WebSocket connection
if path == '/':
path = '/index.html'
response_headers = [
('Server', 'asyncio websocket server'),
('Connection', 'close'),
]
# Derive full system path
full_path = os.path.realpath(os.path.join(sever_root, path[1:]))
# Validate the path
if os.path.commonpath((sever_root, full_path)) != sever_root or \
not os.path.exists(full_path) or not os.path.isfile(full_path):
print("HTTP GET {} 404 NOT FOUND".format(path))
return HTTPStatus.NOT_FOUND, [], b'404 NOT FOUND'
# Guess file content type
extension = full_path.split(".")[-1]
mime_type = MIME_TYPES.get(extension, "application/octet-stream")
response_headers.append(('Content-Type', mime_type))
# Read the whole file into memory and send it out
body = open(full_path, 'rb').read()
response_headers.append(('Content-Length', str(len(body))))
print("HTTP GET {} 200 OK".format(path))
return HTTPStatus.OK, response_headers, body
async def time(websocket, path):
print("New WebSocket connection from", websocket.remote_address)
while websocket.open:
now = datetime.datetime.utcnow().isoformat() + 'Z'
await websocket.send(now)
await asyncio.sleep(random.random() * 2)
# This print will not run when abrnomal websocket close happens
# for example when tcp connection dies and no websocket close frame is sent
print("WebSocket connection closed for", websocket.remote_address)
if __name__ == "__main__":
# set first argument for the handler to current working directory
handler = functools.partial(process_request, os.getcwd())
start_server = websockets.serve(time, '127.0.0.1', 8765,
process_request=handler)
print("Running server at http://127.0.0.1:8765/")
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
@artizirk
Copy link
Author

@dg9vh It looks like that error only happens when using websockets version 7.0. And looks like Debian 10 is currently shipping that version in the repos. If possible, use venv and install latest 8.x release of websockets from pypi.

@dg9vh
Copy link

dg9vh commented Dec 14, 2020

Hi,
tnx for your info... will try it if I get it managed :-) tnx!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment