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

```

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,

{"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"}]}