unwebsockify/unwebsockify.py
2020-06-30 12:20:20 -04:00

106 lines
3.4 KiB
Python
Executable File

#!/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)