##// END OF EJS Templates
Merge pull request #3089 from minrk/stdin...
Brian E. Granger -
r10380:c3a90443 merge
parent child Browse files
Show More
@@ -507,6 +507,12 b' class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):'
507 # under Python 2.x for some reason
507 # under Python 2.x for some reason
508 msg = msg.encode('utf8', 'replace')
508 msg = msg.encode('utf8', 'replace')
509 try:
509 try:
510 identity, msg = msg.split(':', 1)
511 self.session.session = identity.decode('ascii')
512 except Exception:
513 logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
514
515 try:
510 self.request._cookies = Cookie.SimpleCookie(msg)
516 self.request._cookies = Cookie.SimpleCookie(msg)
511 except:
517 except:
512 self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
518 self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
@@ -519,23 +525,28 b' class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):'
519 self.on_message = self.save_on_message
525 self.on_message = self.save_on_message
520
526
521
527
522 class IOPubHandler(AuthenticatedZMQStreamHandler):
528 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
523
529
530 @property
531 def max_msg_size(self):
532 return self.settings.get('max_msg_size', 65535)
533
534 def create_stream(self):
535 km = self.kernel_manager
536 meth = getattr(km, 'connect_%s' % self.channel)
537 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
538
524 def initialize(self, *args, **kwargs):
539 def initialize(self, *args, **kwargs):
525 self.iopub_stream = None
540 self.zmq_stream = None
526
541
527 def on_first_message(self, msg):
542 def on_first_message(self, msg):
528 try:
543 try:
529 super(IOPubHandler, self).on_first_message(msg)
544 super(ZMQChannelHandler, self).on_first_message(msg)
530 except web.HTTPError:
545 except web.HTTPError:
531 self.close()
546 self.close()
532 return
547 return
533 km = self.kernel_manager
534 kernel_id = self.kernel_id
535 km.add_restart_callback(kernel_id, self.on_kernel_restarted)
536 km.add_restart_callback(kernel_id, self.on_restart_failed, 'dead')
537 try:
548 try:
538 self.iopub_stream = km.connect_iopub(kernel_id)
549 self.create_stream()
539 except web.HTTPError:
550 except web.HTTPError:
540 # WebSockets don't response to traditional error codes so we
551 # WebSockets don't response to traditional error codes so we
541 # close the connection.
552 # close the connection.
@@ -543,29 +554,32 b' class IOPubHandler(AuthenticatedZMQStreamHandler):'
543 self.stream.close()
554 self.stream.close()
544 self.close()
555 self.close()
545 else:
556 else:
546 self.iopub_stream.on_recv(self._on_zmq_reply)
557 self.zmq_stream.on_recv(self._on_zmq_reply)
547
558
548 def on_message(self, msg):
559 def on_message(self, msg):
549 pass
560 if len(msg) < self.max_msg_size:
550
561 msg = jsonapi.loads(msg)
551 def _send_status_message(self, status):
562 self.session.send(self.zmq_stream, msg)
552 msg = self.session.msg("status",
553 {'execution_state': status}
554 )
555 self.write_message(jsonapi.dumps(msg, default=date_default))
556
557 def on_kernel_restarted(self):
558 self.log.warn("kernel %s restarted", self.kernel_id)
559 self._send_status_message('restarting')
560
561 def on_restart_failed(self):
562 self.log.error("kernel %s restarted failed!", self.kernel_id)
563 self._send_status_message('dead')
564
563
565 def on_close(self):
564 def on_close(self):
566 # This method can be called twice, once by self.kernel_died and once
565 # This method can be called twice, once by self.kernel_died and once
567 # from the WebSocket close event. If the WebSocket connection is
566 # from the WebSocket close event. If the WebSocket connection is
568 # closed before the ZMQ streams are setup, they could be None.
567 # closed before the ZMQ streams are setup, they could be None.
568 if self.zmq_stream is not None and not self.zmq_stream.closed():
569 self.zmq_stream.on_recv(None)
570 self.zmq_stream.close()
571
572
573 class IOPubHandler(ZMQChannelHandler):
574 channel = 'iopub'
575
576 def create_stream(self):
577 super(IOPubHandler, self).create_stream()
578 km = self.kernel_manager
579 km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
580 km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
581
582 def on_close(self):
569 km = self.kernel_manager
583 km = self.kernel_manager
570 if self.kernel_id in km:
584 if self.kernel_id in km:
571 km.remove_restart_callback(
585 km.remove_restart_callback(
@@ -574,48 +588,31 b' class IOPubHandler(AuthenticatedZMQStreamHandler):'
574 km.remove_restart_callback(
588 km.remove_restart_callback(
575 self.kernel_id, self.on_restart_failed, 'dead',
589 self.kernel_id, self.on_restart_failed, 'dead',
576 )
590 )
577 if self.iopub_stream is not None and not self.iopub_stream.closed():
591 super(IOPubHandler, self).on_close()
578 self.iopub_stream.on_recv(None)
579 self.iopub_stream.close()
580
581
582 class ShellHandler(AuthenticatedZMQStreamHandler):
583
592
584 @property
593 def _send_status_message(self, status):
585 def max_msg_size(self):
594 msg = self.session.msg("status",
586 return self.settings.get('max_msg_size', 65535)
595 {'execution_state': status}
587
596 )
588 def initialize(self, *args, **kwargs):
597 self.write_message(jsonapi.dumps(msg, default=date_default))
589 self.shell_stream = None
590
598
591 def on_first_message(self, msg):
599 def on_kernel_restarted(self):
592 try:
600 logging.warn("kernel %s restarted", self.kernel_id)
593 super(ShellHandler, self).on_first_message(msg)
601 self._send_status_message('restarting')
594 except web.HTTPError:
595 self.close()
596 return
597 km = self.kernel_manager
598 kernel_id = self.kernel_id
599 try:
600 self.shell_stream = km.connect_shell(kernel_id)
601 except web.HTTPError:
602 # WebSockets don't response to traditional error codes so we
603 # close the connection.
604 if not self.stream.closed():
605 self.stream.close()
606 self.close()
607 else:
608 self.shell_stream.on_recv(self._on_zmq_reply)
609
602
603 def on_restart_failed(self):
604 logging.error("kernel %s restarted failed!", self.kernel_id)
605 self._send_status_message('dead')
606
610 def on_message(self, msg):
607 def on_message(self, msg):
611 if len(msg) < self.max_msg_size:
608 """IOPub messages make no sense"""
612 msg = jsonapi.loads(msg)
609 pass
613 self.session.send(self.shell_stream, msg)
614
610
615 def on_close(self):
611 class ShellHandler(ZMQChannelHandler):
616 # Make sure the stream exists and is not already closed.
612 channel = 'shell'
617 if self.shell_stream is not None and not self.shell_stream.closed():
613
618 self.shell_stream.close()
614 class StdinHandler(ZMQChannelHandler):
615 channel = 'stdin'
619
616
620
617
621 #-----------------------------------------------------------------------------
618 #-----------------------------------------------------------------------------
@@ -66,7 +66,7 b' from IPython.frontend.html.notebook import DEFAULT_STATIC_FILES_PATH'
66 from .kernelmanager import MappingKernelManager
66 from .kernelmanager import MappingKernelManager
67 from .handlers import (LoginHandler, LogoutHandler,
67 from .handlers import (LoginHandler, LogoutHandler,
68 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
68 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
69 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
69 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler, StdinHandler,
70 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
70 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
71 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
71 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
72 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
72 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
@@ -160,6 +160,7 b' class NotebookWebApplication(web.Application):'
160 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
160 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
161 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
161 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
162 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
162 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
163 (r"/kernels/%s/stdin" % _kernel_id_regex, StdinHandler),
163 (r"/notebooks", NotebookRootHandler),
164 (r"/notebooks", NotebookRootHandler),
164 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
165 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
165 (r"/rstservice/render", RSTHandler),
166 (r"/rstservice/render", RSTHandler),
@@ -936,6 +936,9 b' pre,code,kbd,samp{white-space:pre-wrap;}'
936 a{text-decoration:underline;}
936 a{text-decoration:underline;}
937 p{margin-bottom:0;}
937 p{margin-bottom:0;}
938 a.heading-anchor:link,a.heading-anchor:visited{text-decoration:none;color:inherit;}
938 a.heading-anchor:link,a.heading-anchor:visited{text-decoration:none;color:inherit;}
939 div.raw_input{padding-top:0px;padding-bottom:0px;height:1em;line-height:1em;font-family:monospace;}
940 span.input_prompt{font-family:inherit;}
941 input.raw_input{font-family:inherit;font-size:inherit;color:inherit;width:auto;margin:-2px 0px 0px 1px;padding-left:1px;padding-top:2px;height:1em;}
939 @media print{body{overflow:visible !important;} div#notebook{overflow:visible !important;} .ui-widget-content{border:0px;} #save_widget{margin:0px !important;} #header,#pager,#pager_splitter,#menubar,#toolbar{display:none !important;} .cell{border:none !important;} .toolbar{display:none;}}.rendered_html{color:black;}.rendered_html em{font-style:italic;}
942 @media print{body{overflow:visible !important;} div#notebook{overflow:visible !important;} .ui-widget-content{border:0px;} #save_widget{margin:0px !important;} #header,#pager,#pager_splitter,#menubar,#toolbar{display:none !important;} .cell{border:none !important;} .toolbar{display:none;}}.rendered_html{color:black;}.rendered_html em{font-style:italic;}
940 .rendered_html strong{font-weight:bold;}
943 .rendered_html strong{font-weight:bold;}
941 .rendered_html u{text-decoration:underline;}
944 .rendered_html u{text-decoration:underline;}
@@ -245,7 +245,8 b' var IPython = (function (IPython) {'
245 'execute_reply': $.proxy(this._handle_execute_reply, this),
245 'execute_reply': $.proxy(this._handle_execute_reply, this),
246 'output': $.proxy(this.output_area.handle_output, this.output_area),
246 'output': $.proxy(this.output_area.handle_output, this.output_area),
247 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
247 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
248 'set_next_input': $.proxy(this._handle_set_next_input, this)
248 'set_next_input': $.proxy(this._handle_set_next_input, this),
249 'input_request': $.proxy(this._handle_input_request, this)
249 };
250 };
250 var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false});
251 var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false});
251 };
252 };
@@ -260,10 +261,23 b' var IPython = (function (IPython) {'
260 $([IPython.events]).trigger('set_dirty.Notebook', {'value': true});
261 $([IPython.events]).trigger('set_dirty.Notebook', {'value': true});
261 }
262 }
262
263
264 /**
265 * @method _handle_set_next_input
266 * @private
267 */
263 CodeCell.prototype._handle_set_next_input = function (text) {
268 CodeCell.prototype._handle_set_next_input = function (text) {
264 var data = {'cell': this, 'text': text}
269 var data = {'cell': this, 'text': text}
265 $([IPython.events]).trigger('set_next_input.Notebook', data);
270 $([IPython.events]).trigger('set_next_input.Notebook', data);
266 }
271 }
272
273 /**
274 * @method _handle_input_request
275 * @private
276 */
277 CodeCell.prototype._handle_input_request = function (content) {
278 this.output_area.append_raw_input(content);
279 }
280
267
281
268 // Basic cell manipulation.
282 // Basic cell manipulation.
269
283
@@ -28,6 +28,7 b' var IPython = (function (IPython) {'
28 this.kernel_id = null;
28 this.kernel_id = null;
29 this.shell_channel = null;
29 this.shell_channel = null;
30 this.iopub_channel = null;
30 this.iopub_channel = null;
31 this.stdin_channel = null;
31 this.base_url = base_url;
32 this.base_url = base_url;
32 this.running = false;
33 this.running = false;
33 this.username = "username";
34 this.username = "username";
@@ -127,9 +128,12 b' var IPython = (function (IPython) {'
127 var ws_url = this.ws_url + this.kernel_url;
128 var ws_url = this.ws_url + this.kernel_url;
128 console.log("Starting WebSockets:", ws_url);
129 console.log("Starting WebSockets:", ws_url);
129 this.shell_channel = new this.WebSocket(ws_url + "/shell");
130 this.shell_channel = new this.WebSocket(ws_url + "/shell");
131 this.stdin_channel = new this.WebSocket(ws_url + "/stdin");
130 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
132 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
131 send_cookie = function(){
133 send_cookie = function(){
132 this.send(document.cookie);
134 // send the session id so the Session object Python-side
135 // has the same identity
136 this.send(that.session_id + ':' + document.cookie);
133 };
137 };
134 var already_called_onclose = false; // only alert once
138 var already_called_onclose = false; // only alert once
135 var ws_closed_early = function(evt){
139 var ws_closed_early = function(evt){
@@ -150,21 +154,26 b' var IPython = (function (IPython) {'
150 that._websocket_closed(ws_url, false);
154 that._websocket_closed(ws_url, false);
151 }
155 }
152 };
156 };
153 this.shell_channel.onopen = send_cookie;
157 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
154 this.shell_channel.onclose = ws_closed_early;
158 for (var i=0; i < channels.length; i++) {
155 this.iopub_channel.onopen = send_cookie;
159 channels[i].onopen = send_cookie;
156 this.iopub_channel.onclose = ws_closed_early;
160 channels[i].onclose = ws_closed_early;
161 }
157 // switch from early-close to late-close message after 1s
162 // switch from early-close to late-close message after 1s
158 setTimeout(function() {
163 setTimeout(function() {
159 if (that.shell_channel !== null) {
164 for (var i=0; i < channels.length; i++) {
160 that.shell_channel.onclose = ws_closed_late;
165 if (channels[i] !== null) {
161 }
166 channels[i].onclose = ws_closed_late;
162 if (that.iopub_channel !== null) {
167 }
163 that.iopub_channel.onclose = ws_closed_late;
164 }
168 }
165 }, 1000);
169 }, 1000);
166 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
170 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
167 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
171 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this);
172 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
173
174 $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
175 that.send_input_reply(data);
176 });
168 };
177 };
169
178
170 /**
179 /**
@@ -172,16 +181,14 b' var IPython = (function (IPython) {'
172 * @method stop_channels
181 * @method stop_channels
173 */
182 */
174 Kernel.prototype.stop_channels = function () {
183 Kernel.prototype.stop_channels = function () {
175 if (this.shell_channel !== null) {
184 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
176 this.shell_channel.onclose = function (evt) {};
185 for (var i=0; i < channels.length; i++) {
177 this.shell_channel.close();
186 if ( channels[i] !== null ) {
178 this.shell_channel = null;
187 channels[i].onclose = function (evt) {};
179 };
188 channels[i].close();
180 if (this.iopub_channel !== null) {
189 }
181 this.iopub_channel.onclose = function (evt) {};
182 this.iopub_channel.close();
183 this.iopub_channel = null;
184 };
190 };
191 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
185 };
192 };
186
193
187 // Main public methods.
194 // Main public methods.
@@ -284,6 +291,9 b' var IPython = (function (IPython) {'
284 user_expressions : {},
291 user_expressions : {},
285 allow_stdin : false
292 allow_stdin : false
286 };
293 };
294 if (callbacks.input_request !== undefined) {
295 content.allow_stdin = true;
296 }
287 $.extend(true, content, options)
297 $.extend(true, content, options)
288 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
298 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
289 var msg = this._get_msg("execute_request", content);
299 var msg = this._get_msg("execute_request", content);
@@ -343,8 +353,18 b' var IPython = (function (IPython) {'
343 };
353 };
344 };
354 };
345
355
356 Kernel.prototype.send_input_reply = function (input) {
357 var content = {
358 value : input,
359 };
360 $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
361 var msg = this._get_msg("input_reply", content);
362 this.stdin_channel.send(JSON.stringify(msg));
363 return msg.header.msg_id;
364 };
365
346
366
347 // Reply handlers.
367 // Reply handlers
348
368
349 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
369 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
350 var callbacks = this._msg_callbacks[msg_id];
370 var callbacks = this._msg_callbacks[msg_id];
@@ -433,6 +453,26 b' var IPython = (function (IPython) {'
433 };
453 };
434
454
435
455
456 Kernel.prototype._handle_input_request = function (e) {
457 var request = $.parseJSON(e.data);
458 var header = request.header;
459 var content = request.content;
460 var metadata = request.metadata;
461 var msg_type = header.msg_type;
462 if (msg_type !== 'input_request') {
463 console.log("Invalid input request!", request);
464 return;
465 }
466 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
467 if (callbacks !== undefined) {
468 var cb = callbacks[msg_type];
469 if (cb !== undefined) {
470 cb(content, metadata);
471 }
472 };
473 };
474
475
436 IPython.Kernel = Kernel;
476 IPython.Kernel = Kernel;
437
477
438 return IPython;
478 return IPython;
@@ -448,6 +448,55 b' var IPython = (function (IPython) {'
448 toinsert.append(latex);
448 toinsert.append(latex);
449 element.append(toinsert);
449 element.append(toinsert);
450 };
450 };
451
452 OutputArea.prototype.append_raw_input = function (content) {
453 var that = this;
454 this.expand();
455 this.flush_clear_timeout();
456 var area = this.create_output_area();
457
458 area.append(
459 $("<div/>")
460 .addClass("box-flex1 output_subarea raw_input")
461 .append(
462 $("<span/>")
463 .addClass("input_prompt")
464 .text(content.prompt)
465 )
466 .append(
467 $("<input/>")
468 .addClass("raw_input")
469 .attr('type', 'text')
470 .attr("size", 80)
471 .keydown(function (event, ui) {
472 // make sure we submit on enter,
473 // and don't re-execute the *cell* on shift-enter
474 if (event.which === utils.keycodes.ENTER) {
475 that._submit_raw_input();
476 return false;
477 }
478 })
479 )
480 );
481 this.element.append(area);
482 area.find("input.raw_input").focus();
483 }
484 OutputArea.prototype._submit_raw_input = function (evt) {
485 var container = this.element.find("div.raw_input");
486 var theprompt = container.find("span.input_prompt");
487 var theinput = container.find("input.raw_input");
488 var value = theinput.attr("value");
489 var content = {
490 output_type : 'stream',
491 name : 'stdout',
492 text : theprompt.text() + value + '\n'
493 }
494 // remove form container
495 container.parent().remove();
496 // replace with plaintext version in stdout
497 this.append_output(content, false);
498 $([IPython.events]).trigger('send_input_reply.Kernel', value);
499 }
451
500
452
501
453 OutputArea.prototype.handle_clear_output = function (content) {
502 OutputArea.prototype.handle_clear_output = function (content) {
@@ -477,3 +477,26 b' a.heading-anchor:link, a.heading-anchor:visited {'
477 text-decoration: none;
477 text-decoration: none;
478 color: inherit;
478 color: inherit;
479 }
479 }
480
481 /* raw_input styles */
482
483 div.raw_input {
484 padding-top: 0px;
485 padding-bottom: 0px;
486 height: 1em;
487 line-height: 1em;
488 font-family: monospace;
489 }
490 span.input_prompt {
491 font-family: inherit;
492 }
493 input.raw_input {
494 font-family: inherit;
495 font-size: inherit;
496 color: inherit;
497 width: auto;
498 margin: -2px 0px 0px 1px;
499 padding-left: 1px;
500 padding-top: 2px;
501 height: 1em;
502 }
@@ -746,7 +746,16 b' class Kernel(Configurable):'
746 # Flush output before making the request.
746 # Flush output before making the request.
747 sys.stderr.flush()
747 sys.stderr.flush()
748 sys.stdout.flush()
748 sys.stdout.flush()
749
749 # flush the stdin socket, to purge stale replies
750 while True:
751 try:
752 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
753 except zmq.ZMQError as e:
754 if e.errno == zmq.EAGAIN:
755 break
756 else:
757 raise
758
750 # Send the input request.
759 # Send the input request.
751 content = json_clean(dict(prompt=prompt))
760 content = json_clean(dict(prompt=prompt))
752 self.session.send(self.stdin_socket, u'input_request', content, parent,
761 self.session.send(self.stdin_socket, u'input_request', content, parent,
General Comments 0
You need to be logged in to leave comments. Login now