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