From 4eff3a05d44f06a8366481481eac4fae46e050b4 Mon Sep 17 00:00:00 2001 From: Jim Paris Date: Tue, 30 Jun 2020 12:20:20 -0400 Subject: [PATCH] Initial commit --- unwebsockify.py | 105 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100755 unwebsockify.py diff --git a/unwebsockify.py b/unwebsockify.py new file mode 100755 index 0000000..b89607d --- /dev/null +++ b/unwebsockify.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 + +desc = """\ +Unwebsockify is a TCP to WebSocket proxy/bridge. It accepts a +plain TCP connection and connects to a WebSocket server, effectively +adding WS support to a client that does not natively support it. It +is essentially the opposite of "websockify". + +Note that this only handles simple byte streams of data, with no +support for conveying WebSockets message framing back to the client. +In most cases, specifying the WebSockets subprotocol is necessary. + +For example, Eclipse Mosquitto supports WebSockets on the server side, +but not on the client side (for bridging). To connect one instance +to another, run + + {prog} --port 13232 --subproto mqtt wss://server/ + +and configure the client with e.g. + + address 127.0.0.1:13232 +""" + +import sys + +import asyncio +import websockets + +class Proxy: + def __init__(self, port, addr, url, subproto): + self.port = port + self.addr = addr + self.url = url + if subproto: + self.subproto = [ subproto ] + else: + self.subproto = None + + async def copy(self, reader, writer): + while True: + data = await reader() + if data == b'': + break + future = writer(data) + if future: + await future + + async def handle_client(self, r, w): + peer = w.get_extra_info("peername") + print(f'{peer} connected') + loop = asyncio.get_event_loop() + try: + async with websockets.connect( + self.url, subprotocols=self.subproto) as ws: + print(f'{peer} connected to {self.url}') + def r_reader(): + return r.read(65536) + tcp_to_ws = loop.create_task(self.copy(r_reader, ws.send)) + ws_to_tcp = loop.create_task(self.copy(ws.recv, w.write)) + done, pending = await asyncio.wait([tcp_to_ws, ws_to_tcp], + return_when=asyncio.FIRST_COMPLETED) + for x in done: + try: + await x + except: + pass + for x in pending: + x.cancel() + except Exception as e: + print(f'{peer} exception:', e) + w.close() + print(f'{peer} closed') + + async def start(self): + await asyncio.start_server(self.handle_client, self.addr, self.port) + print(f'Listening on {self.addr} port {self.port}') + + +def main(argv): + import argparse + import textwrap + + parser = argparse.ArgumentParser( + prog=argv[0], + formatter_class=argparse.RawDescriptionHelpFormatter, + description=textwrap.indent(desc.format(prog=argv[0]), prefix=" ")) + + parser.add_argument("--port", "-p", metavar="PORT", default=13232, + help="TCP listen port") + parser.add_argument("--listen", "-l", metavar="ADDR", default="0.0.0.0", + help="TCP listen address") + parser.add_argument("--subproto", "-s", metavar="SUBPROTO", default=None, + help="WebSocket subprotocol") + parser.add_argument("url", metavar="URL", + help="WebSocket URL (ws://.. or wss://..)") + + args = parser.parse_args() + + loop = asyncio.get_event_loop() + proxy = Proxy(args.port, args.listen, args.url, args.subproto) + loop.run_until_complete(proxy.start()) + loop.run_forever() + +if __name__ == "__main__": + main(sys.argv)