Inspired by ipython-notebook-proxy and based on ipydra , and extending the latter to support more complex user authentication, as well as a proxy server, since in my use case only port 80 can be opened.
I use flask-sockets for working gunicorn , but I am having problems with proxies. IPython uses three different WebSockets connections, /shell , /stdin and /iopub , but I can only get 101 Switching Protocols for the first two. And /stdin gets Connection Close Frame right after creation.
This is the excerpt code in question:
# Flask imports... from werkzeug import LocalProxy from ws4py.client.geventclient import WebSocketClient # I use my own LocalProxy because flask-sockets does not support Werkzeug Rules websocket = LocalProxy(lambda: request.environ.get('wsgi.websocket', None)) websockets = {} PROXY_DOMAIN = "127.0.0.1:8888" # IPython host and port methods = ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH", "CONNECT"] @app.route('/', defaults={'url': ''}, methods=methods) @app.route('/<path:url>', methods=methods) def proxy(url): with app.test_request_context(): if websocket: while True: data = websocket.receive() websocket_url = 'ws://{}/{}'.format(PROXY_DOMAIN, url) if websocket_url not in websockets: client = WebSocketClient(websocket_url, protocols=['http-only', 'chat']) websockets[websocket_url] = client else: client = websockets[websocket_url] client.connect() if data: client.send(data) client_data = client.receive() if client_data: websocket.send(client_data) return Response()
I also tried to create my own WebSocket proxy class, but it does not work either.
class WebSocketProxy(WebSocketClient): def __init__(self, to, *args, **kwargs): self.to = to print(("Proxy to", self.to)) super(WebSocketProxy, self).__init__(*args, **kwargs) def opened(self): m = self.to.receive() print("<= %d %s" % (len(m), str(m))) self.send(m) def closed(self, code, reason): print(("Closed down", code, reason)) def received_message(self, m): print("=> %d %s" % (len(m), str(m))) self.to.send(m)
The normal request-response loop works like a charm, so I deleted this code. If you are interested, the full code is hosted in hidra .
I start the server using
$ gunicorn -k flask_sockets.worker hidra:app