Unwebsockify is a TCP to WebSocket proxy/bridge -- like a reverse websockify.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

106 lines
3.4 KiB

  1. #!/usr/bin/env python3
  2. desc = """\
  3. Unwebsockify is a TCP to WebSocket proxy/bridge. It accepts a
  4. plain TCP connection and connects to a WebSocket server, effectively
  5. adding WS support to a client that does not natively support it. It
  6. is essentially the opposite of "websockify".
  7. Note that this only handles simple byte streams of data, with no
  8. support for conveying WebSockets message framing back to the client.
  9. In most cases, specifying the WebSockets subprotocol is necessary.
  10. For example, Eclipse Mosquitto supports WebSockets on the server side,
  11. but not on the client side (for bridging). To connect one instance
  12. to another, run
  13. {prog} --port 13232 --subproto mqtt wss://server/
  14. and configure the client with e.g.
  15. address 127.0.0.1:13232
  16. """
  17. import sys
  18. import asyncio
  19. import websockets
  20. class Proxy:
  21. def __init__(self, port, addr, url, subproto):
  22. self.port = port
  23. self.addr = addr
  24. self.url = url
  25. if subproto:
  26. self.subproto = [ subproto ]
  27. else:
  28. self.subproto = None
  29. async def copy(self, reader, writer):
  30. while True:
  31. data = await reader()
  32. if data == b'':
  33. break
  34. future = writer(data)
  35. if future:
  36. await future
  37. async def handle_client(self, r, w):
  38. peer = w.get_extra_info("peername")
  39. print(f'{peer} connected')
  40. loop = asyncio.get_event_loop()
  41. try:
  42. async with websockets.connect(
  43. self.url, subprotocols=self.subproto) as ws:
  44. print(f'{peer} connected to {self.url}')
  45. def r_reader():
  46. return r.read(65536)
  47. tcp_to_ws = loop.create_task(self.copy(r_reader, ws.send))
  48. ws_to_tcp = loop.create_task(self.copy(ws.recv, w.write))
  49. done, pending = await asyncio.wait([tcp_to_ws, ws_to_tcp],
  50. return_when=asyncio.FIRST_COMPLETED)
  51. for x in done:
  52. try:
  53. await x
  54. except:
  55. pass
  56. for x in pending:
  57. x.cancel()
  58. except Exception as e:
  59. print(f'{peer} exception:', e)
  60. w.close()
  61. print(f'{peer} closed')
  62. async def start(self):
  63. await asyncio.start_server(self.handle_client, self.addr, self.port)
  64. print(f'Listening on {self.addr} port {self.port}')
  65. def main(argv):
  66. import argparse
  67. import textwrap
  68. parser = argparse.ArgumentParser(
  69. prog=argv[0],
  70. formatter_class=argparse.RawDescriptionHelpFormatter,
  71. description=textwrap.indent(desc.format(prog=argv[0]), prefix=" "))
  72. parser.add_argument("--port", "-p", metavar="PORT", default=13232,
  73. help="TCP listen port")
  74. parser.add_argument("--listen", "-l", metavar="ADDR", default="0.0.0.0",
  75. help="TCP listen address")
  76. parser.add_argument("--subproto", "-s", metavar="SUBPROTO", default=None,
  77. help="WebSocket subprotocol")
  78. parser.add_argument("url", metavar="URL",
  79. help="WebSocket URL (ws://.. or wss://..)")
  80. args = parser.parse_args()
  81. loop = asyncio.get_event_loop()
  82. proxy = Proxy(args.port, args.listen, args.url, args.subproto)
  83. loop.run_until_complete(proxy.start())
  84. loop.run_forever()
  85. if __name__ == "__main__":
  86. main(sys.argv)