prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Use WebSocket.ping to keep connections alive across proxies
  I am accessing a remote IPython Notebook and need to use a proxy (Squid/SOCKS) for internet access.
  As long as I am running cells everything works fine, but after one minute of inactivity the proxy seems to close the connection, resulting in the following Firefox console log:
  
  ```
  The connection to wss://example.org/kernels/$uuid/shell was interrupted while the page 
  was loading. @ https://example.org/static/js/kernel.js
  ```
  
  The websocket connection is reestablished immediately (and interrupted again after one minute), but running cells does not work anymore until I manually restart the kernel.
  
  ```
  ipython --version
  0.13.1.rc2
  ```
  
  Sending any message via each websocket (shell+iopub) will avoid interruption. This is a quick and _dirty_ client side workaround:
  
  ```
  from IPython.core.display import HTML
  HTML('''<script>
  var keepalive_timer;
  function keep_connection_alive() {
      IPython.notebook.kernel.execute();
      keepalive_timer = setTimeout('keep_connection_alive()', 55000);
  }
  keep_connection_alive();
  </script>''')
  ```
  
  WebSocket has ping and pong frames for keep-alive, tornado has a ping() function to send such a frame. Could the ping function be called somewhere in the kernel code to keep alive connections even via proxy?
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `IPython/html/base/zmqhandlers.py`
  Content:
  ```
  1 """Tornado handlers for WebSocket <-> ZMQ sockets."""
  2 
  3 # Copyright (c) IPython Development Team.
  4 # Distributed under the terms of the Modified BSD License.
  5 
  6 import json
  7 
  8 try:
  9     from urllib.parse import urlparse # Py 3
  10 except ImportError:
  11     from urlparse import urlparse # Py 2
  12 
  13 try:
  14     from http.cookies import SimpleCookie  # Py 3
  15 except ImportError:
  16     from Cookie import SimpleCookie  # Py 2
  17 import logging
  18 
  19 import tornado
  20 from tornado import web
  21 from tornado import websocket
  22 
  23 from IPython.kernel.zmq.session import Session
  24 from IPython.utils.jsonutil import date_default
  25 from IPython.utils.py3compat import PY3, cast_unicode
  26 
  27 from .handlers import IPythonHandler
  28 
  29 
  30 class ZMQStreamHandler(websocket.WebSocketHandler):
  31     
  32     def check_origin(self, origin):
  33         """Check Origin == Host or Access-Control-Allow-Origin.
  34         
  35         Tornado >= 4 calls this method automatically, raising 403 if it returns False.
  36         We call it explicitly in `open` on Tornado < 4.
  37         """
  38         if self.allow_origin == '*':
  39             return True
  40 
  41         host = self.request.headers.get("Host")
  42 
  43         # If no header is provided, assume we can't verify origin
  44         if(origin is None or host is None):
  45             return False
  46         
  47         host_origin = "{0}://{1}".format(self.request.protocol, host)
  48         
  49         # OK if origin matches host
  50         if origin == host_origin:
  51             return True
  52         
  53         # Check CORS headers
  54         if self.allow_origin:
  55             return self.allow_origin == origin
  56         elif self.allow_origin_pat:
  57             return bool(self.allow_origin_pat.match(origin))
  58         else:
  59             # No CORS headers deny the request
  60             return False
  61 
  62     def clear_cookie(self, *args, **kwargs):
  63         """meaningless for websockets"""
  64         pass
  65 
  66     def _reserialize_reply(self, msg_list):
  67         """Reserialize a reply message using JSON.
  68 
  69         This takes the msg list from the ZMQ socket, unserializes it using
  70         self.session and then serializes the result using JSON. This method
  71         should be used by self._on_zmq_reply to build messages that can
  72         be sent back to the browser.
  73         """
  74         idents, msg_list = self.session.feed_identities(msg_list)
  75         msg = self.session.unserialize(msg_list)
  76         try:
  77             msg['header'].pop('date')
  78         except KeyError:
  79             pass
  80         try:
  81             msg['parent_header'].pop('date')
  82         except KeyError:
  83             pass
  84         msg.pop('buffers')
  85         return json.dumps(msg, default=date_default)
  86 
  87     def _on_zmq_reply(self, msg_list):
  88         # Sometimes this gets triggered when the on_close method is scheduled in the
  89         # eventloop but hasn't been called.
  90         if self.stream.closed(): return
  91         try:
  92             msg = self._reserialize_reply(msg_list)
  93         except Exception:
  94             self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
  95         else:
  96             self.write_message(msg)
  97 
  98     def allow_draft76(self):
  99         """Allow draft 76, until browsers such as Safari update to RFC 6455.
  100         
  101         This has been disabled by default in tornado in release 2.2.0, and
  102         support will be removed in later versions.
  103         """
  104         return True
  105 
  106 
  107 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
  108     def set_default_headers(self):
  109         """Undo the set_default_headers in IPythonHandler
  110         
  111         which doesn't make sense for websockets
  112         """
  113         pass
  114 
  115     def open(self, kernel_id):
  116         self.kernel_id = cast_unicode(kernel_id, 'ascii')
  117         # Check to see that origin matches host directly, including ports
  118         # Tornado 4 already does CORS checking
  119         if tornado.version_info[0] < 4:
  120             if not self.check_origin(self.get_origin()):
  121                 self.log.warn("Cross Origin WebSocket Attempt from %s", self.get_origin())
  122                 raise web.HTTPError(403)
  123 
  124         self.session = Session(config=self.config)
  125         self.save_on_message = self.on_message
  126         self.on_message = self.on_first_message
  127 
  128     def _inject_cookie_message(self, msg):
  129         """Inject the first message, which is the document cookie,
  130         for authentication."""
  131         if not PY3 and isinstance(msg, unicode):
  132             # Cookie constructor doesn't accept unicode strings
  133             # under Python 2.x for some reason
  134             msg = msg.encode('utf8', 'replace')
  135         try:
  136             identity, msg = msg.split(':', 1)
  137             self.session.session = cast_unicode(identity, 'ascii')
  138         except Exception:
  139             logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
  140         
  141         try:
  142             self.request._cookies = SimpleCookie(msg)
  143         except:
  144             self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
  145 
  146     def on_first_message(self, msg):
  147         self._inject_cookie_message(msg)
  148         if self.get_current_user() is None:
  149             self.log.warn("Couldn't authenticate WebSocket connection")
  150             raise web.HTTPError(403)
  151         self.on_message = self.save_on_message
  152
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py
  --- a/IPython/html/base/zmqhandlers.py
  +++ b/IPython/html/base/zmqhandlers.py
  @@ -17,6 +17,7 @@
   import logging
   
   import tornado
  +from tornado import ioloop
   from tornado import web
   from tornado import websocket
   
  @@ -103,8 +104,12 @@
           """
           return True
   
  +# ping interval for keeping websockets alive (30 seconds)
  +WS_PING_INTERVAL = 30000
   
   class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
  +    ping_callback = None
  +
       def set_default_headers(self):
           """Undo the set_default_headers in IPythonHandler
           
  @@ -124,6 +129,16 @@
           self.session = Session(config=self.config)
           self.save_on_message = self.on_message
           self.on_message = self.on_first_message
  +        self.ping_callback = ioloop.PeriodicCallback(self.send_ping, WS_PING_INTERVAL)
  +        self.ping_callback.start()
  +
  +    def send_ping(self):
  +        """send a ping to keep the websocket alive"""
  +        if self.stream.closed() and self.ping_callback is not None:
  +            self.ping_callback.stop()
  +            return
  +
  +        self.ping(b'')
   
       def _inject_cookie_message(self, msg):
           """Inject the first message, which is the document cookie,
  
verification_info: {"golden_diff": "diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py\n--- a/IPython/html/base/zmqhandlers.py\n+++ b/IPython/html/base/zmqhandlers.py\n@@ -17,6 +17,7 @@\n import logging\n \n import tornado\n+from tornado import ioloop\n from tornado import web\n from tornado import websocket\n \n@@ -103,8 +104,12 @@\n         \"\"\"\n         return True\n \n+# ping interval for keeping websockets alive (30 seconds)\n+WS_PING_INTERVAL = 30000\n \n class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):\n+    ping_callback = None\n+\n     def set_default_headers(self):\n         \"\"\"Undo the set_default_headers in IPythonHandler\n         \n@@ -124,6 +129,16 @@\n         self.session = Session(config=self.config)\n         self.save_on_message = self.on_message\n         self.on_message = self.on_first_message\n+        self.ping_callback = ioloop.PeriodicCallback(self.send_ping, WS_PING_INTERVAL)\n+        self.ping_callback.start()\n+\n+    def send_ping(self):\n+        \"\"\"send a ping to keep the websocket alive\"\"\"\n+        if self.stream.closed() and self.ping_callback is not None:\n+            self.ping_callback.stop()\n+            return\n+\n+        self.ping(b'')\n \n     def _inject_cookie_message(self, msg):\n         \"\"\"Inject the first message, which is the document cookie,\n", "issue": "Use WebSocket.ping to keep connections alive across proxies\nI am accessing a remote IPython Notebook and need to use a proxy (Squid/SOCKS) for internet access.\nAs long as I am running cells everything works fine, but after one minute of inactivity the proxy seems to close the connection, resulting in the following Firefox console log:\n\n```\nThe connection to wss://example.org/kernels/$uuid/shell was interrupted while the page \nwas loading. @ https://example.org/static/js/kernel.js\n```\n\nThe websocket connection is reestablished immediately (and interrupted again after one minute), but running cells does not work anymore until I manually restart the kernel.\n\n```\nipython --version\n0.13.1.rc2\n```\n\nSending any message via each websocket (shell+iopub) will avoid interruption. This is a quick and _dirty_ client side workaround:\n\n```\nfrom IPython.core.display import HTML\nHTML('''<script>\nvar keepalive_timer;\nfunction keep_connection_alive() {\n    IPython.notebook.kernel.execute();\n    keepalive_timer = setTimeout('keep_connection_alive()', 55000);\n}\nkeep_connection_alive();\n</script>''')\n```\n\nWebSocket has ping and pong frames for keep-alive, tornado has a ping() function to send such a frame. Could the ping function be called somewhere in the kernel code to keep alive connections even via proxy?\n\n", "before_files": [{"content": "\"\"\"Tornado handlers for WebSocket <-> ZMQ sockets.\"\"\"\n\n# Copyright (c) IPython Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nimport json\n\ntry:\n    from urllib.parse import urlparse # Py 3\nexcept ImportError:\n    from urlparse import urlparse # Py 2\n\ntry:\n    from http.cookies import SimpleCookie  # Py 3\nexcept ImportError:\n    from Cookie import SimpleCookie  # Py 2\nimport logging\n\nimport tornado\nfrom tornado import web\nfrom tornado import websocket\n\nfrom IPython.kernel.zmq.session import Session\nfrom IPython.utils.jsonutil import date_default\nfrom IPython.utils.py3compat import PY3, cast_unicode\n\nfrom .handlers import IPythonHandler\n\n\nclass ZMQStreamHandler(websocket.WebSocketHandler):\n    \n    def check_origin(self, origin):\n        \"\"\"Check Origin == Host or Access-Control-Allow-Origin.\n        \n        Tornado >= 4 calls this method automatically, raising 403 if it returns False.\n        We call it explicitly in `open` on Tornado < 4.\n        \"\"\"\n        if self.allow_origin == '*':\n            return True\n\n        host = self.request.headers.get(\"Host\")\n\n        # If no header is provided, assume we can't verify origin\n        if(origin is None or host is None):\n            return False\n        \n        host_origin = \"{0}://{1}\".format(self.request.protocol, host)\n        \n        # OK if origin matches host\n        if origin == host_origin:\n            return True\n        \n        # Check CORS headers\n        if self.allow_origin:\n            return self.allow_origin == origin\n        elif self.allow_origin_pat:\n            return bool(self.allow_origin_pat.match(origin))\n        else:\n            # No CORS headers deny the request\n            return False\n\n    def clear_cookie(self, *args, **kwargs):\n        \"\"\"meaningless for websockets\"\"\"\n        pass\n\n    def _reserialize_reply(self, msg_list):\n        \"\"\"Reserialize a reply message using JSON.\n\n        This takes the msg list from the ZMQ socket, unserializes it using\n        self.session and then serializes the result using JSON. This method\n        should be used by self._on_zmq_reply to build messages that can\n        be sent back to the browser.\n        \"\"\"\n        idents, msg_list = self.session.feed_identities(msg_list)\n        msg = self.session.unserialize(msg_list)\n        try:\n            msg['header'].pop('date')\n        except KeyError:\n            pass\n        try:\n            msg['parent_header'].pop('date')\n        except KeyError:\n            pass\n        msg.pop('buffers')\n        return json.dumps(msg, default=date_default)\n\n    def _on_zmq_reply(self, msg_list):\n        # Sometimes this gets triggered when the on_close method is scheduled in the\n        # eventloop but hasn't been called.\n        if self.stream.closed(): return\n        try:\n            msg = self._reserialize_reply(msg_list)\n        except Exception:\n            self.log.critical(\"Malformed message: %r\" % msg_list, exc_info=True)\n        else:\n            self.write_message(msg)\n\n    def allow_draft76(self):\n        \"\"\"Allow draft 76, until browsers such as Safari update to RFC 6455.\n        \n        This has been disabled by default in tornado in release 2.2.0, and\n        support will be removed in later versions.\n        \"\"\"\n        return True\n\n\nclass AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):\n    def set_default_headers(self):\n        \"\"\"Undo the set_default_headers in IPythonHandler\n        \n        which doesn't make sense for websockets\n        \"\"\"\n        pass\n\n    def open(self, kernel_id):\n        self.kernel_id = cast_unicode(kernel_id, 'ascii')\n        # Check to see that origin matches host directly, including ports\n        # Tornado 4 already does CORS checking\n        if tornado.version_info[0] < 4:\n            if not self.check_origin(self.get_origin()):\n                self.log.warn(\"Cross Origin WebSocket Attempt from %s\", self.get_origin())\n                raise web.HTTPError(403)\n\n        self.session = Session(config=self.config)\n        self.save_on_message = self.on_message\n        self.on_message = self.on_first_message\n\n    def _inject_cookie_message(self, msg):\n        \"\"\"Inject the first message, which is the document cookie,\n        for authentication.\"\"\"\n        if not PY3 and isinstance(msg, unicode):\n            # Cookie constructor doesn't accept unicode strings\n            # under Python 2.x for some reason\n            msg = msg.encode('utf8', 'replace')\n        try:\n            identity, msg = msg.split(':', 1)\n            self.session.session = cast_unicode(identity, 'ascii')\n        except Exception:\n            logging.error(\"First ws message didn't have the form 'identity:[cookie]' - %r\", msg)\n        \n        try:\n            self.request._cookies = SimpleCookie(msg)\n        except:\n            self.log.warn(\"couldn't parse cookie string: %s\",msg, exc_info=True)\n\n    def on_first_message(self, msg):\n        self._inject_cookie_message(msg)\n        if self.get_current_user() is None:\n            self.log.warn(\"Couldn't authenticate WebSocket connection\")\n            raise web.HTTPError(403)\n        self.on_message = self.save_on_message\n", "path": "IPython/html/base/zmqhandlers.py"}], "after_files": [{"content": "\"\"\"Tornado handlers for WebSocket <-> ZMQ sockets.\"\"\"\n\n# Copyright (c) IPython Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nimport json\n\ntry:\n    from urllib.parse import urlparse # Py 3\nexcept ImportError:\n    from urlparse import urlparse # Py 2\n\ntry:\n    from http.cookies import SimpleCookie  # Py 3\nexcept ImportError:\n    from Cookie import SimpleCookie  # Py 2\nimport logging\n\nimport tornado\nfrom tornado import ioloop\nfrom tornado import web\nfrom tornado import websocket\n\nfrom IPython.kernel.zmq.session import Session\nfrom IPython.utils.jsonutil import date_default\nfrom IPython.utils.py3compat import PY3, cast_unicode\n\nfrom .handlers import IPythonHandler\n\n\nclass ZMQStreamHandler(websocket.WebSocketHandler):\n    \n    def check_origin(self, origin):\n        \"\"\"Check Origin == Host or Access-Control-Allow-Origin.\n        \n        Tornado >= 4 calls this method automatically, raising 403 if it returns False.\n        We call it explicitly in `open` on Tornado < 4.\n        \"\"\"\n        if self.allow_origin == '*':\n            return True\n\n        host = self.request.headers.get(\"Host\")\n\n        # If no header is provided, assume we can't verify origin\n        if(origin is None or host is None):\n            return False\n        \n        host_origin = \"{0}://{1}\".format(self.request.protocol, host)\n        \n        # OK if origin matches host\n        if origin == host_origin:\n            return True\n        \n        # Check CORS headers\n        if self.allow_origin:\n            return self.allow_origin == origin\n        elif self.allow_origin_pat:\n            return bool(self.allow_origin_pat.match(origin))\n        else:\n            # No CORS headers deny the request\n            return False\n\n    def clear_cookie(self, *args, **kwargs):\n        \"\"\"meaningless for websockets\"\"\"\n        pass\n\n    def _reserialize_reply(self, msg_list):\n        \"\"\"Reserialize a reply message using JSON.\n\n        This takes the msg list from the ZMQ socket, unserializes it using\n        self.session and then serializes the result using JSON. This method\n        should be used by self._on_zmq_reply to build messages that can\n        be sent back to the browser.\n        \"\"\"\n        idents, msg_list = self.session.feed_identities(msg_list)\n        msg = self.session.unserialize(msg_list)\n        try:\n            msg['header'].pop('date')\n        except KeyError:\n            pass\n        try:\n            msg['parent_header'].pop('date')\n        except KeyError:\n            pass\n        msg.pop('buffers')\n        return json.dumps(msg, default=date_default)\n\n    def _on_zmq_reply(self, msg_list):\n        # Sometimes this gets triggered when the on_close method is scheduled in the\n        # eventloop but hasn't been called.\n        if self.stream.closed(): return\n        try:\n            msg = self._reserialize_reply(msg_list)\n        except Exception:\n            self.log.critical(\"Malformed message: %r\" % msg_list, exc_info=True)\n        else:\n            self.write_message(msg)\n\n    def allow_draft76(self):\n        \"\"\"Allow draft 76, until browsers such as Safari update to RFC 6455.\n        \n        This has been disabled by default in tornado in release 2.2.0, and\n        support will be removed in later versions.\n        \"\"\"\n        return True\n\n# ping interval for keeping websockets alive (30 seconds)\nWS_PING_INTERVAL = 30000\n\nclass AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):\n    ping_callback = None\n\n    def set_default_headers(self):\n        \"\"\"Undo the set_default_headers in IPythonHandler\n        \n        which doesn't make sense for websockets\n        \"\"\"\n        pass\n\n    def open(self, kernel_id):\n        self.kernel_id = cast_unicode(kernel_id, 'ascii')\n        # Check to see that origin matches host directly, including ports\n        # Tornado 4 already does CORS checking\n        if tornado.version_info[0] < 4:\n            if not self.check_origin(self.get_origin()):\n                self.log.warn(\"Cross Origin WebSocket Attempt from %s\", self.get_origin())\n                raise web.HTTPError(403)\n\n        self.session = Session(config=self.config)\n        self.save_on_message = self.on_message\n        self.on_message = self.on_first_message\n        self.ping_callback = ioloop.PeriodicCallback(self.send_ping, WS_PING_INTERVAL)\n        self.ping_callback.start()\n\n    def send_ping(self):\n        \"\"\"send a ping to keep the websocket alive\"\"\"\n        if self.stream.closed() and self.ping_callback is not None:\n            self.ping_callback.stop()\n            return\n\n        self.ping(b'')\n\n    def _inject_cookie_message(self, msg):\n        \"\"\"Inject the first message, which is the document cookie,\n        for authentication.\"\"\"\n        if not PY3 and isinstance(msg, unicode):\n            # Cookie constructor doesn't accept unicode strings\n            # under Python 2.x for some reason\n            msg = msg.encode('utf8', 'replace')\n        try:\n            identity, msg = msg.split(':', 1)\n            self.session.session = cast_unicode(identity, 'ascii')\n        except Exception:\n            logging.error(\"First ws message didn't have the form 'identity:[cookie]' - %r\", msg)\n        \n        try:\n            self.request._cookies = SimpleCookie(msg)\n        except:\n            self.log.warn(\"couldn't parse cookie string: %s\",msg, exc_info=True)\n\n    def on_first_message(self, msg):\n        self._inject_cookie_message(msg)\n        if self.get_current_user() is None:\n            self.log.warn(\"Couldn't authenticate WebSocket connection\")\n            raise web.HTTPError(403)\n        self.on_message = self.save_on_message\n", "path": "IPython/html/base/zmqhandlers.py"}]}