106 lines
3.4 KiB
Python
Executable File
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)
|