We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Reconnecting error messages with master(-ish)
Version: ee6223ab74eb

My situation is that I set up a remote notebook on a server that I only can reach when in a VPN. Now, my Wifi is shaky for some reason, that's possibly how my VPN get's shaky.
So I needed to close my notebook after a VPN reconnect and the dashboard still reported it as 'running'. When I clicked on the 'running' notebook, I got this error in the console:

``` python
RunTimeError: Method not supported by Web Sockets
```

Full error log here:
https://gist.github.com/23ed2e252897d96804a5

Working on OSX 10.9.5 using conda and Python 3.4

Reconnecting error messages with master(-ish)
Version: ee6223ab74eb

My situation is that I set up a remote notebook on a server that I only can reach when in a VPN. Now, my Wifi is shaky for some reason, that's possibly how my VPN get's shaky.
So I needed to close my notebook after a VPN reconnect and the dashboard still reported it as 'running'. When I clicked on the 'running' notebook, I got this error in the console:

``` python
RunTimeError: Method not supported by Web Sockets
```

Full error log here:
https://gist.github.com/23ed2e252897d96804a5

Working on OSX 10.9.5 using conda and Python 3.4


--- 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 # coding: utf-8
2 """Tornado handlers for WebSocket <-> ZMQ sockets."""
3 
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
6 
7 import os
8 import json
9 import struct
10 import warnings
11 
12 try:
13     from urllib.parse import urlparse # Py 3
14 except ImportError:
15     from urlparse import urlparse # Py 2
16 
17 import tornado
18 from tornado import gen, ioloop, web
19 from tornado.websocket import WebSocketHandler
20 
21 from IPython.kernel.zmq.session import Session
22 from IPython.utils.jsonutil import date_default, extract_dates
23 from IPython.utils.py3compat import cast_unicode
24 
25 from .handlers import IPythonHandler
26 
27 def serialize_binary_message(msg):
28     """serialize a message as a binary blob
29 
30     Header:
31 
32     4 bytes: number of msg parts (nbufs) as 32b int
33     4 * nbufs bytes: offset for each buffer as integer as 32b int
34 
35     Offsets are from the start of the buffer, including the header.
36 
37     Returns
38     -------
39 
40     The message serialized to bytes.
41 
42     """
43     # don't modify msg or buffer list in-place
44     msg = msg.copy()
45     buffers = list(msg.pop('buffers'))
46     bmsg = json.dumps(msg, default=date_default).encode('utf8')
47     buffers.insert(0, bmsg)
48     nbufs = len(buffers)
49     offsets = [4 * (nbufs + 1)]
50     for buf in buffers[:-1]:
51         offsets.append(offsets[-1] + len(buf))
52     offsets_buf = struct.pack('!' + 'I' * (nbufs + 1), nbufs, *offsets)
53     buffers.insert(0, offsets_buf)
54     return b''.join(buffers)
55 
56 
57 def deserialize_binary_message(bmsg):
58     """deserialize a message from a binary blog
59 
60     Header:
61 
62     4 bytes: number of msg parts (nbufs) as 32b int
63     4 * nbufs bytes: offset for each buffer as integer as 32b int
64 
65     Offsets are from the start of the buffer, including the header.
66 
67     Returns
68     -------
69 
70     message dictionary
71     """
72     nbufs = struct.unpack('!i', bmsg[:4])[0]
73     offsets = list(struct.unpack('!' + 'I' * nbufs, bmsg[4:4*(nbufs+1)]))
74     offsets.append(None)
75     bufs = []
76     for start, stop in zip(offsets[:-1], offsets[1:]):
77         bufs.append(bmsg[start:stop])
78     msg = json.loads(bufs[0].decode('utf8'))
79     msg['header'] = extract_dates(msg['header'])
80     msg['parent_header'] = extract_dates(msg['parent_header'])
81     msg['buffers'] = bufs[1:]
82     return msg
83 
84 # ping interval for keeping websockets alive (30 seconds)
85 WS_PING_INTERVAL = 30000
86 
87 if os.environ.get('IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS', False):
88     warnings.warn("""Allowing draft76 websocket connections!
89     This should only be done for testing with phantomjs!""")
90     from IPython.html import allow76
91     WebSocketHandler = allow76.AllowDraftWebSocketHandler
92     # draft 76 doesn't support ping
93     WS_PING_INTERVAL = 0
94 
95 class ZMQStreamHandler(WebSocketHandler):
96     
97     def check_origin(self, origin):
98         """Check Origin == Host or Access-Control-Allow-Origin.
99         
100         Tornado >= 4 calls this method automatically, raising 403 if it returns False.
101         We call it explicitly in `open` on Tornado < 4.
102         """
103         if self.allow_origin == '*':
104             return True
105 
106         host = self.request.headers.get("Host")
107 
108         # If no header is provided, assume we can't verify origin
109         if origin is None:
110             self.log.warn("Missing Origin header, rejecting WebSocket connection.")
111             return False
112         if host is None:
113             self.log.warn("Missing Host header, rejecting WebSocket connection.")
114             return False
115         
116         origin = origin.lower()
117         origin_host = urlparse(origin).netloc
118         
119         # OK if origin matches host
120         if origin_host == host:
121             return True
122         
123         # Check CORS headers
124         if self.allow_origin:
125             allow = self.allow_origin == origin
126         elif self.allow_origin_pat:
127             allow = bool(self.allow_origin_pat.match(origin))
128         else:
129             # No CORS headers deny the request
130             allow = False
131         if not allow:
132             self.log.warn("Blocking Cross Origin WebSocket Attempt.  Origin: %s, Host: %s",
133                 origin, host,
134             )
135         return allow
136 
137     def clear_cookie(self, *args, **kwargs):
138         """meaningless for websockets"""
139         pass
140 
141     def _reserialize_reply(self, msg_list, channel=None):
142         """Reserialize a reply message using JSON.
143 
144         This takes the msg list from the ZMQ socket, deserializes it using
145         self.session and then serializes the result using JSON. This method
146         should be used by self._on_zmq_reply to build messages that can
147         be sent back to the browser.
148         """
149         idents, msg_list = self.session.feed_identities(msg_list)
150         msg = self.session.deserialize(msg_list)
151         if channel:
152             msg['channel'] = channel
153         if msg['buffers']:
154             buf = serialize_binary_message(msg)
155             return buf
156         else:
157             smsg = json.dumps(msg, default=date_default)
158             return cast_unicode(smsg)
159 
160     def _on_zmq_reply(self, stream, msg_list):
161         # Sometimes this gets triggered when the on_close method is scheduled in the
162         # eventloop but hasn't been called.
163         if stream.closed(): return
164         channel = getattr(stream, 'channel', None)
165         try:
166             msg = self._reserialize_reply(msg_list, channel=channel)
167         except Exception:
168             self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
169         else:
170             self.write_message(msg, binary=isinstance(msg, bytes))
171 
172 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
173     ping_callback = None
174     last_ping = 0
175     last_pong = 0
176     
177     @property
178     def ping_interval(self):
179         """The interval for websocket keep-alive pings.
180         
181         Set ws_ping_interval = 0 to disable pings.
182         """
183         return self.settings.get('ws_ping_interval', WS_PING_INTERVAL)
184     
185     @property
186     def ping_timeout(self):
187         """If no ping is received in this many milliseconds,
188         close the websocket connection (VPNs, etc. can fail to cleanly close ws connections).
189         Default is max of 3 pings or 30 seconds.
190         """
191         return self.settings.get('ws_ping_timeout',
192             max(3 * self.ping_interval, WS_PING_INTERVAL)
193         )
194 
195     def set_default_headers(self):
196         """Undo the set_default_headers in IPythonHandler
197         
198         which doesn't make sense for websockets
199         """
200         pass
201     
202     def pre_get(self):
203         """Run before finishing the GET request
204         
205         Extend this method to add logic that should fire before
206         the websocket finishes completing.
207         """
208         # authenticate the request before opening the websocket
209         if self.get_current_user() is None:
210             self.log.warn("Couldn't authenticate WebSocket connection")
211             raise web.HTTPError(403)
212         
213         if self.get_argument('session_id', False):
214             self.session.session = cast_unicode(self.get_argument('session_id'))
215         else:
216             self.log.warn("No session ID specified")
217     
218     @gen.coroutine
219     def get(self, *args, **kwargs):
220         # pre_get can be a coroutine in subclasses
221         # assign and yield in two step to avoid tornado 3 issues
222         res = self.pre_get()
223         yield gen.maybe_future(res)
224         super(AuthenticatedZMQStreamHandler, self).get(*args, **kwargs)
225     
226     def initialize(self):
227         self.log.debug("Initializing websocket connection %s", self.request.path)
228         self.session = Session(config=self.config)
229     
230     def open(self, *args, **kwargs):
231         self.log.debug("Opening websocket %s", self.request.path)
232         
233         # start the pinging
234         if self.ping_interval > 0:
235             loop = ioloop.IOLoop.current()
236             self.last_ping = loop.time()  # Remember time of last ping
237             self.last_pong = self.last_ping
238             self.ping_callback = ioloop.PeriodicCallback(
239                 self.send_ping, self.ping_interval, io_loop=loop,
240             )
241             self.ping_callback.start()
242 
243     def send_ping(self):
244         """send a ping to keep the websocket alive"""
245         if self.stream.closed() and self.ping_callback is not None:
246             self.ping_callback.stop()
247             return
248         
249         # check for timeout on pong.  Make sure that we really have sent a recent ping in
250         # case the machine with both server and client has been suspended since the last ping.
251         now = ioloop.IOLoop.current().time()
252         since_last_pong = 1e3 * (now - self.last_pong)
253         since_last_ping = 1e3 * (now - self.last_ping)
254         if since_last_ping < 2*self.ping_interval and since_last_pong > self.ping_timeout:
255             self.log.warn("WebSocket ping timeout after %i ms.", since_last_pong)
256             self.close()
257             return
258 
259         self.ping(b'')
260         self.last_ping = now
261 
262     def on_pong(self, data):
263         self.last_pong = ioloop.IOLoop.current().time()
264
```
--- 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
@@ -94,11 +94,23 @@
 
 class ZMQStreamHandler(WebSocketHandler):
     
+    if tornado.version_info < (4,1):
+        """Backport send_error from tornado 4.1 to 4.0"""
+        def send_error(self, *args, **kwargs):
+            if self.stream is None:
+                super(WebSocketHandler, self).send_error(*args, **kwargs)
+            else:
+                # If we get an uncaught exception during the handshake,
+                # we have no choice but to abruptly close the connection.
+                # TODO: for uncaught exceptions after the handshake,
+                # we can close the connection more gracefully.
+                self.stream.close()
+
+    
     def check_origin(self, origin):
         """Check Origin == Host or Access-Control-Allow-Origin.
         
         Tornado >= 4 calls this method automatically, raising 403 if it returns False.
-        We call it explicitly in `open` on Tornado < 4.
         """
         if self.allow_origin == '*':
             return True
@@ -160,7 +172,10 @@
     def _on_zmq_reply(self, stream, msg_list):
         # Sometimes this gets triggered when the on_close method is scheduled in the
         # eventloop but hasn't been called.
-        if stream.closed(): return
+        if self.stream.closed() or stream.closed():
+            self.log.warn("zmq message arrived on closed channel")
+            self.close()
+            return
         channel = getattr(stream, 'channel', None)
         try:
             msg = self._reserialize_reply(msg_list, channel=channel)

{"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@@ -94,11 +94,23 @@\n \n class ZMQStreamHandler(WebSocketHandler):\n     \n+    if tornado.version_info < (4,1):\n+        \"\"\"Backport send_error from tornado 4.1 to 4.0\"\"\"\n+        def send_error(self, *args, **kwargs):\n+            if self.stream is None:\n+                super(WebSocketHandler, self).send_error(*args, **kwargs)\n+            else:\n+                # If we get an uncaught exception during the handshake,\n+                # we have no choice but to abruptly close the connection.\n+                # TODO: for uncaught exceptions after the handshake,\n+                # we can close the connection more gracefully.\n+                self.stream.close()\n+\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@@ -160,7 +172,10 @@\n     def _on_zmq_reply(self, stream, 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 stream.closed(): return\n+        if self.stream.closed() or stream.closed():\n+            self.log.warn(\"zmq message arrived on closed channel\")\n+            self.close()\n+            return\n         channel = getattr(stream, 'channel', None)\n         try:\n             msg = self._reserialize_reply(msg_list, channel=channel)\n", "issue": "Reconnecting error messages with master(-ish)\nVersion: ee6223ab74eb\n\nMy situation is that I set up a remote notebook on a server that I only can reach when in a VPN. Now, my Wifi is shaky for some reason, that's possibly how my VPN get's shaky.\nSo I needed to close my notebook after a VPN reconnect and the dashboard still reported it as 'running'. When I clicked on the 'running' notebook, I got this error in the console:\n\n``` python\nRunTimeError: Method not supported by Web Sockets\n```\n\nFull error log here:\nhttps://gist.github.com/23ed2e252897d96804a5\n\nWorking on OSX 10.9.5 using conda and Python 3.4\n\nReconnecting error messages with master(-ish)\nVersion: ee6223ab74eb\n\nMy situation is that I set up a remote notebook on a server that I only can reach when in a VPN. Now, my Wifi is shaky for some reason, that's possibly how my VPN get's shaky.\nSo I needed to close my notebook after a VPN reconnect and the dashboard still reported it as 'running'. When I clicked on the 'running' notebook, I got this error in the console:\n\n``` python\nRunTimeError: Method not supported by Web Sockets\n```\n\nFull error log here:\nhttps://gist.github.com/23ed2e252897d96804a5\n\nWorking on OSX 10.9.5 using conda and Python 3.4\n\n", "before_files": [{"content": "# coding: utf-8\n\"\"\"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 os\nimport json\nimport struct\nimport warnings\n\ntry:\n    from urllib.parse import urlparse # Py 3\nexcept ImportError:\n    from urlparse import urlparse # Py 2\n\nimport tornado\nfrom tornado import gen, ioloop, web\nfrom tornado.websocket import WebSocketHandler\n\nfrom IPython.kernel.zmq.session import Session\nfrom IPython.utils.jsonutil import date_default, extract_dates\nfrom IPython.utils.py3compat import cast_unicode\n\nfrom .handlers import IPythonHandler\n\ndef serialize_binary_message(msg):\n    \"\"\"serialize a message as a binary blob\n\n    Header:\n\n    4 bytes: number of msg parts (nbufs) as 32b int\n    4 * nbufs bytes: offset for each buffer as integer as 32b int\n\n    Offsets are from the start of the buffer, including the header.\n\n    Returns\n    -------\n\n    The message serialized to bytes.\n\n    \"\"\"\n    # don't modify msg or buffer list in-place\n    msg = msg.copy()\n    buffers = list(msg.pop('buffers'))\n    bmsg = json.dumps(msg, default=date_default).encode('utf8')\n    buffers.insert(0, bmsg)\n    nbufs = len(buffers)\n    offsets = [4 * (nbufs + 1)]\n    for buf in buffers[:-1]:\n        offsets.append(offsets[-1] + len(buf))\n    offsets_buf = struct.pack('!' + 'I' * (nbufs + 1), nbufs, *offsets)\n    buffers.insert(0, offsets_buf)\n    return b''.join(buffers)\n\n\ndef deserialize_binary_message(bmsg):\n    \"\"\"deserialize a message from a binary blog\n\n    Header:\n\n    4 bytes: number of msg parts (nbufs) as 32b int\n    4 * nbufs bytes: offset for each buffer as integer as 32b int\n\n    Offsets are from the start of the buffer, including the header.\n\n    Returns\n    -------\n\n    message dictionary\n    \"\"\"\n    nbufs = struct.unpack('!i', bmsg[:4])[0]\n    offsets = list(struct.unpack('!' + 'I' * nbufs, bmsg[4:4*(nbufs+1)]))\n    offsets.append(None)\n    bufs = []\n    for start, stop in zip(offsets[:-1], offsets[1:]):\n        bufs.append(bmsg[start:stop])\n    msg = json.loads(bufs[0].decode('utf8'))\n    msg['header'] = extract_dates(msg['header'])\n    msg['parent_header'] = extract_dates(msg['parent_header'])\n    msg['buffers'] = bufs[1:]\n    return msg\n\n# ping interval for keeping websockets alive (30 seconds)\nWS_PING_INTERVAL = 30000\n\nif os.environ.get('IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS', False):\n    warnings.warn(\"\"\"Allowing draft76 websocket connections!\n    This should only be done for testing with phantomjs!\"\"\")\n    from IPython.html import allow76\n    WebSocketHandler = allow76.AllowDraftWebSocketHandler\n    # draft 76 doesn't support ping\n    WS_PING_INTERVAL = 0\n\nclass ZMQStreamHandler(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:\n            self.log.warn(\"Missing Origin header, rejecting WebSocket connection.\")\n            return False\n        if host is None:\n            self.log.warn(\"Missing Host header, rejecting WebSocket connection.\")\n            return False\n        \n        origin = origin.lower()\n        origin_host = urlparse(origin).netloc\n        \n        # OK if origin matches host\n        if origin_host == host:\n            return True\n        \n        # Check CORS headers\n        if self.allow_origin:\n            allow = self.allow_origin == origin\n        elif self.allow_origin_pat:\n            allow = bool(self.allow_origin_pat.match(origin))\n        else:\n            # No CORS headers deny the request\n            allow = False\n        if not allow:\n            self.log.warn(\"Blocking Cross Origin WebSocket Attempt.  Origin: %s, Host: %s\",\n                origin, host,\n            )\n        return allow\n\n    def clear_cookie(self, *args, **kwargs):\n        \"\"\"meaningless for websockets\"\"\"\n        pass\n\n    def _reserialize_reply(self, msg_list, channel=None):\n        \"\"\"Reserialize a reply message using JSON.\n\n        This takes the msg list from the ZMQ socket, deserializes 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.deserialize(msg_list)\n        if channel:\n            msg['channel'] = channel\n        if msg['buffers']:\n            buf = serialize_binary_message(msg)\n            return buf\n        else:\n            smsg = json.dumps(msg, default=date_default)\n            return cast_unicode(smsg)\n\n    def _on_zmq_reply(self, stream, 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 stream.closed(): return\n        channel = getattr(stream, 'channel', None)\n        try:\n            msg = self._reserialize_reply(msg_list, channel=channel)\n        except Exception:\n            self.log.critical(\"Malformed message: %r\" % msg_list, exc_info=True)\n        else:\n            self.write_message(msg, binary=isinstance(msg, bytes))\n\nclass AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):\n    ping_callback = None\n    last_ping = 0\n    last_pong = 0\n    \n    @property\n    def ping_interval(self):\n        \"\"\"The interval for websocket keep-alive pings.\n        \n        Set ws_ping_interval = 0 to disable pings.\n        \"\"\"\n        return self.settings.get('ws_ping_interval', WS_PING_INTERVAL)\n    \n    @property\n    def ping_timeout(self):\n        \"\"\"If no ping is received in this many milliseconds,\n        close the websocket connection (VPNs, etc. can fail to cleanly close ws connections).\n        Default is max of 3 pings or 30 seconds.\n        \"\"\"\n        return self.settings.get('ws_ping_timeout',\n            max(3 * self.ping_interval, WS_PING_INTERVAL)\n        )\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 pre_get(self):\n        \"\"\"Run before finishing the GET request\n        \n        Extend this method to add logic that should fire before\n        the websocket finishes completing.\n        \"\"\"\n        # authenticate the request before opening the websocket\n        if self.get_current_user() is None:\n            self.log.warn(\"Couldn't authenticate WebSocket connection\")\n            raise web.HTTPError(403)\n        \n        if self.get_argument('session_id', False):\n            self.session.session = cast_unicode(self.get_argument('session_id'))\n        else:\n            self.log.warn(\"No session ID specified\")\n    \n    @gen.coroutine\n    def get(self, *args, **kwargs):\n        # pre_get can be a coroutine in subclasses\n        # assign and yield in two step to avoid tornado 3 issues\n        res = self.pre_get()\n        yield gen.maybe_future(res)\n        super(AuthenticatedZMQStreamHandler, self).get(*args, **kwargs)\n    \n    def initialize(self):\n        self.log.debug(\"Initializing websocket connection %s\", self.request.path)\n        self.session = Session(config=self.config)\n    \n    def open(self, *args, **kwargs):\n        self.log.debug(\"Opening websocket %s\", self.request.path)\n        \n        # start the pinging\n        if self.ping_interval > 0:\n            loop = ioloop.IOLoop.current()\n            self.last_ping = loop.time()  # Remember time of last ping\n            self.last_pong = self.last_ping\n            self.ping_callback = ioloop.PeriodicCallback(\n                self.send_ping, self.ping_interval, io_loop=loop,\n            )\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        # check for timeout on pong.  Make sure that we really have sent a recent ping in\n        # case the machine with both server and client has been suspended since the last ping.\n        now = ioloop.IOLoop.current().time()\n        since_last_pong = 1e3 * (now - self.last_pong)\n        since_last_ping = 1e3 * (now - self.last_ping)\n        if since_last_ping < 2*self.ping_interval and since_last_pong > self.ping_timeout:\n            self.log.warn(\"WebSocket ping timeout after %i ms.\", since_last_pong)\n            self.close()\n            return\n\n        self.ping(b'')\n        self.last_ping = now\n\n    def on_pong(self, data):\n        self.last_pong = ioloop.IOLoop.current().time()\n", "path": "IPython/html/base/zmqhandlers.py"}], "after_files": [{"content": "# coding: utf-8\n\"\"\"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 os\nimport json\nimport struct\nimport warnings\n\ntry:\n    from urllib.parse import urlparse # Py 3\nexcept ImportError:\n    from urlparse import urlparse # Py 2\n\nimport tornado\nfrom tornado import gen, ioloop, web\nfrom tornado.websocket import WebSocketHandler\n\nfrom IPython.kernel.zmq.session import Session\nfrom IPython.utils.jsonutil import date_default, extract_dates\nfrom IPython.utils.py3compat import cast_unicode\n\nfrom .handlers import IPythonHandler\n\ndef serialize_binary_message(msg):\n    \"\"\"serialize a message as a binary blob\n\n    Header:\n\n    4 bytes: number of msg parts (nbufs) as 32b int\n    4 * nbufs bytes: offset for each buffer as integer as 32b int\n\n    Offsets are from the start of the buffer, including the header.\n\n    Returns\n    -------\n\n    The message serialized to bytes.\n\n    \"\"\"\n    # don't modify msg or buffer list in-place\n    msg = msg.copy()\n    buffers = list(msg.pop('buffers'))\n    bmsg = json.dumps(msg, default=date_default).encode('utf8')\n    buffers.insert(0, bmsg)\n    nbufs = len(buffers)\n    offsets = [4 * (nbufs + 1)]\n    for buf in buffers[:-1]:\n        offsets.append(offsets[-1] + len(buf))\n    offsets_buf = struct.pack('!' + 'I' * (nbufs + 1), nbufs, *offsets)\n    buffers.insert(0, offsets_buf)\n    return b''.join(buffers)\n\n\ndef deserialize_binary_message(bmsg):\n    \"\"\"deserialize a message from a binary blog\n\n    Header:\n\n    4 bytes: number of msg parts (nbufs) as 32b int\n    4 * nbufs bytes: offset for each buffer as integer as 32b int\n\n    Offsets are from the start of the buffer, including the header.\n\n    Returns\n    -------\n\n    message dictionary\n    \"\"\"\n    nbufs = struct.unpack('!i', bmsg[:4])[0]\n    offsets = list(struct.unpack('!' + 'I' * nbufs, bmsg[4:4*(nbufs+1)]))\n    offsets.append(None)\n    bufs = []\n    for start, stop in zip(offsets[:-1], offsets[1:]):\n        bufs.append(bmsg[start:stop])\n    msg = json.loads(bufs[0].decode('utf8'))\n    msg['header'] = extract_dates(msg['header'])\n    msg['parent_header'] = extract_dates(msg['parent_header'])\n    msg['buffers'] = bufs[1:]\n    return msg\n\n# ping interval for keeping websockets alive (30 seconds)\nWS_PING_INTERVAL = 30000\n\nif os.environ.get('IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS', False):\n    warnings.warn(\"\"\"Allowing draft76 websocket connections!\n    This should only be done for testing with phantomjs!\"\"\")\n    from IPython.html import allow76\n    WebSocketHandler = allow76.AllowDraftWebSocketHandler\n    # draft 76 doesn't support ping\n    WS_PING_INTERVAL = 0\n\nclass ZMQStreamHandler(WebSocketHandler):\n    \n    if tornado.version_info < (4,1):\n        \"\"\"Backport send_error from tornado 4.1 to 4.0\"\"\"\n        def send_error(self, *args, **kwargs):\n            if self.stream is None:\n                super(WebSocketHandler, self).send_error(*args, **kwargs)\n            else:\n                # If we get an uncaught exception during the handshake,\n                # we have no choice but to abruptly close the connection.\n                # TODO: for uncaught exceptions after the handshake,\n                # we can close the connection more gracefully.\n                self.stream.close()\n\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        \"\"\"\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:\n            self.log.warn(\"Missing Origin header, rejecting WebSocket connection.\")\n            return False\n        if host is None:\n            self.log.warn(\"Missing Host header, rejecting WebSocket connection.\")\n            return False\n        \n        origin = origin.lower()\n        origin_host = urlparse(origin).netloc\n        \n        # OK if origin matches host\n        if origin_host == host:\n            return True\n        \n        # Check CORS headers\n        if self.allow_origin:\n            allow = self.allow_origin == origin\n        elif self.allow_origin_pat:\n            allow = bool(self.allow_origin_pat.match(origin))\n        else:\n            # No CORS headers deny the request\n            allow = False\n        if not allow:\n            self.log.warn(\"Blocking Cross Origin WebSocket Attempt.  Origin: %s, Host: %s\",\n                origin, host,\n            )\n        return allow\n\n    def clear_cookie(self, *args, **kwargs):\n        \"\"\"meaningless for websockets\"\"\"\n        pass\n\n    def _reserialize_reply(self, msg_list, channel=None):\n        \"\"\"Reserialize a reply message using JSON.\n\n        This takes the msg list from the ZMQ socket, deserializes 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.deserialize(msg_list)\n        if channel:\n            msg['channel'] = channel\n        if msg['buffers']:\n            buf = serialize_binary_message(msg)\n            return buf\n        else:\n            smsg = json.dumps(msg, default=date_default)\n            return cast_unicode(smsg)\n\n    def _on_zmq_reply(self, stream, 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() or stream.closed():\n            self.log.warn(\"zmq message arrived on closed channel\")\n            self.close()\n            return\n        channel = getattr(stream, 'channel', None)\n        try:\n            msg = self._reserialize_reply(msg_list, channel=channel)\n        except Exception:\n            self.log.critical(\"Malformed message: %r\" % msg_list, exc_info=True)\n        else:\n            self.write_message(msg, binary=isinstance(msg, bytes))\n\nclass AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):\n    ping_callback = None\n    last_ping = 0\n    last_pong = 0\n    \n    @property\n    def ping_interval(self):\n        \"\"\"The interval for websocket keep-alive pings.\n        \n        Set ws_ping_interval = 0 to disable pings.\n        \"\"\"\n        return self.settings.get('ws_ping_interval', WS_PING_INTERVAL)\n    \n    @property\n    def ping_timeout(self):\n        \"\"\"If no ping is received in this many milliseconds,\n        close the websocket connection (VPNs, etc. can fail to cleanly close ws connections).\n        Default is max of 3 pings or 30 seconds.\n        \"\"\"\n        return self.settings.get('ws_ping_timeout',\n            max(3 * self.ping_interval, WS_PING_INTERVAL)\n        )\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 pre_get(self):\n        \"\"\"Run before finishing the GET request\n        \n        Extend this method to add logic that should fire before\n        the websocket finishes completing.\n        \"\"\"\n        # authenticate the request before opening the websocket\n        if self.get_current_user() is None:\n            self.log.warn(\"Couldn't authenticate WebSocket connection\")\n            raise web.HTTPError(403)\n        \n        if self.get_argument('session_id', False):\n            self.session.session = cast_unicode(self.get_argument('session_id'))\n        else:\n            self.log.warn(\"No session ID specified\")\n    \n    @gen.coroutine\n    def get(self, *args, **kwargs):\n        # pre_get can be a coroutine in subclasses\n        # assign and yield in two step to avoid tornado 3 issues\n        res = self.pre_get()\n        yield gen.maybe_future(res)\n        super(AuthenticatedZMQStreamHandler, self).get(*args, **kwargs)\n    \n    def initialize(self):\n        self.log.debug(\"Initializing websocket connection %s\", self.request.path)\n        self.session = Session(config=self.config)\n    \n    def open(self, *args, **kwargs):\n        self.log.debug(\"Opening websocket %s\", self.request.path)\n        \n        # start the pinging\n        if self.ping_interval > 0:\n            loop = ioloop.IOLoop.current()\n            self.last_ping = loop.time()  # Remember time of last ping\n            self.last_pong = self.last_ping\n            self.ping_callback = ioloop.PeriodicCallback(\n                self.send_ping, self.ping_interval, io_loop=loop,\n            )\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        # check for timeout on pong.  Make sure that we really have sent a recent ping in\n        # case the machine with both server and client has been suspended since the last ping.\n        now = ioloop.IOLoop.current().time()\n        since_last_pong = 1e3 * (now - self.last_pong)\n        since_last_ping = 1e3 * (now - self.last_ping)\n        if since_last_ping < 2*self.ping_interval and since_last_pong > self.ping_timeout:\n            self.log.warn(\"WebSocket ping timeout after %i ms.\", since_last_pong)\n            self.close()\n            return\n\n        self.ping(b'')\n        self.last_ping = now\n\n    def on_pong(self, data):\n        self.last_pong = ioloop.IOLoop.current().time()\n", "path": "IPython/html/base/zmqhandlers.py"}]}