#!/usr/bin/env python3 import sys, os, asyncio, json, argparse import aiohttp, aiohttp_jinja2, jinja2 from aiohttp import web # from urllib.parse import urlparse, unquote as urlunquote, quote as urlquote async def index (request): ws = web.WebSocketResponse() ws_ready = ws.can_prepare(request) if not ws_ready.ok: return aiohttp_jinja2.render_template(request.app['template'], request, {}) await ws.prepare(request) # print('index.Websocket connection ready') connections = request.app['websockets'] connections.append(ws) await ws.send_str(json.dumps({'src': 'connect', 'connections': len(connections)})) print ("{} active connections".format(len(connections))) # incoming messages async for msg in ws: print ("index.got msg") if msg.type == aiohttp.WSMsgType.TEXT: print(msg.data) if msg.data == 'close': await ws.close() # else: # await ws.send_str(msg.data + '/answer') # if the connection closes, the above loop simply finishes # and we arrive here connections.remove(ws) print ("{} active connections".format(len(connections))) return ws # The following is a fusion of: # https://stackoverflow.com/questions/31510190/aysncio-cannot-read-stdin-on-windows#36785819 # https://docs.aiohttp.org/en/stable/web_advanced.html#background-tasks async def listen_to_stdin(app): loop = asyncio.get_event_loop() try: while True: line = await loop.run_in_executor(None, sys.stdin.readline) for ws in app['websockets']: await ws.send_str(json.dumps({'src': 'stdin', 'line': line.rstrip()})) except asyncio.CancelledError: pass finally: pass async def start_background_tasks(app): app['stdin_listener'] = asyncio.create_task(listen_to_stdin(app)) async def cleanup_background_tasks(app): app['stdin_listener'].cancel() await app['stdin_listener'] def main (): ap = argparse.ArgumentParser("make & serve") ap.add_argument("--host", default="localhost") ap.add_argument("--port", type=int, default=8080) ap.add_argument("--static", nargs=2, default=None, action="append") ap.add_argument("--template", default="pipeserver.html", help="Template for web page to serve") args = ap.parse_args() # On Windows, the default event loop is SelectorEventLoop which does not support run_in_executor. ProactorEventLoop should be used instead. if sys.platform == 'win32': loop = asyncio.ProactorEventLoop() asyncio.set_event_loop(loop) app = web.Application() app['websockets'] = [] template_path, app['template'] = os.path.split(args.template) jinja_env = aiohttp_jinja2.setup( app, loader=jinja2.FileSystemLoader(template_path)) # jinja_env.filters['datetimeformat'] = format_datetime # setup routes app.add_routes([web.get('/', index)]) if args.static: for name, path in args.static: print ("Adding static route {0} -> {1}".format(name, path)) app.router.add_static(name, path) app.on_startup.append(start_background_tasks) app.on_cleanup.append(cleanup_background_tasks) web.run_app(app, host=args.host, port=args.port) if __name__ == "__main__": main()