Browse Source

Initial commit

master
Jim Paris 1 year ago
commit
4eff3a05d4
1 changed files with 105 additions and 0 deletions
  1. +105
    -0
      unwebsockify.py

+ 105
- 0
unwebsockify.py View File

@@ -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)

Loading…
Cancel
Save