##// END OF EJS Templates
Work on the server side of the html notebook.
Brian Granger -
Show More
@@ -0,0 +1,98 b''
1 import signal
2 import sys
3 import uuid
4
5 from IPython.zmq.ipkernel import launch_kernel
6 from session import SessionManager
7
8
9 class KernelManager(object):
10
11 ip = '127.0.0.1'
12
13 def __init__(self, context):
14 self.context = context
15 self._kernels = {}
16
17 @property
18 def kernel_ids(self):
19 return self._kernels.keys()
20
21 def __len__(self):
22 return len(self.kernel_ids)
23
24 def __contains__(self, kernel_id):
25 if kernel_id in self.kernel_ids:
26 return True
27 else:
28 return False
29
30 def start_kernel(self):
31 kid = str(uuid.uuid4())
32 (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel()
33 d = dict(
34 process = process,
35 stdin_port = stdin_port,
36 iopub_port = iopub_port,
37 shell_port = shell_port,
38 hb_port = hb_port,
39 session_manager = SessionManager(self, kid, self.context)
40 )
41 self._kernels[kid] = d
42 return kid
43
44 def kill_kernel(self, kernel_id):
45 kernel_process = self.get_kernel_process(kernel_id)
46 if kernel_process is not None:
47 # Attempt to kill the kernel.
48 try:
49 kernel_process.kill()
50 except OSError, e:
51 # In Windows, we will get an Access Denied error if the process
52 # has already terminated. Ignore it.
53 if not (sys.platform == 'win32' and e.winerror == 5):
54 raise
55 del self._kernels[kernel_id]
56
57 def interrupt_kernel(self, kernel_id):
58 kernel_process = self.get_kernel_process(kernel_id)
59 if kernel_process is not None:
60 if sys.platform == 'win32':
61 from parentpoller import ParentPollerWindows as Poller
62 Poller.send_interrupt(kernel_process.win32_interrupt_event)
63 else:
64 kernel_process.send_signal(signal.SIGINT)
65
66 def signal_kernel(self, kernel_id, signum):
67 """ Sends a signal to the kernel. Note that since only SIGTERM is
68 supported on Windows, this function is only useful on Unix systems.
69 """
70 kernel_process = self.get_kernel_process(kernel_id)
71 if kernel_process is not None:
72 kernel_process.send_signal(signum)
73
74 def get_kernel_process(self, kernel_id):
75 d = self._kernels.get(kernel_id)
76 if d is not None:
77 return d['process']
78 else:
79 raise KeyError("Kernel with id not found: %s" % kernel_id)
80
81 def get_kernel_ports(self, kernel_id):
82 d = self._kernels.get(kernel_id)
83 if d is not None:
84 dcopy = d.copy()
85 dcopy.pop('process')
86 return dcopy
87 else:
88 raise KeyError("Kernel with id not found: %s" % kernel_id)
89
90 def get_session_manager(self, kernel_id):
91 d = self._kernels.get(kernel_id)
92 if d is not None:
93 return d['session_manager']
94 else:
95 raise KeyError("Kernel with id not found: %s" % kernel_id)
96
97
98
@@ -0,0 +1,56 b''
1 import logging
2
3 import zmq
4 from zmq.eventloop.zmqstream import ZMQStream
5
6
7 class SessionManager(object):
8
9 def __init__(self, kernel_manager, kernel_id, context):
10 self.context = context
11 self.kernel_manager = kernel_manager
12 self.kernel_id = kernel_id
13 self._sessions = {}
14
15 def __del__(self):
16 self.stop_all()
17
18 def start_session(self, session_id):
19 ports = self.kernel_manager.get_kernel_ports(self.kernel_id)
20 iopub_stream = self.create_connected_stream(ports['iopub_port'], zmq.SUB)
21 shell_stream = self.create_connected_stream(ports['shell_port'], zmq.XREQ)
22 self._sessions[session_id] = dict(
23 iopub_stream = iopub_stream,
24 shell_stream = shell_stream
25 )
26
27 def stop_session(self, session_id):
28 session_dict = self._sessions.get(session_id)
29 if session_dict is not None:
30 for name, stream in session_dict.items():
31 stream.close()
32 del self._sessions[session_id]
33
34 def stop_all(self):
35 for session_id in self._sessions.keys():
36 self.stop_session(session_id)
37
38 def create_connected_stream(self, port, socket_type):
39 sock = self.context.socket(socket_type)
40 addr = "tcp://%s:%i" % (self.kernel_manager.ip, port)
41 logging.info("Connecting to: %s" % addr)
42 sock.connect(addr)
43 return ZMQStream(sock)
44
45 def get_stream(self, session_id, stream_name):
46 session_dict = self._sessions.get(session_id)
47 if session_dict is not None:
48 return session_dict[stream_name]
49 else:
50 raise KeyError("Session with id not found: %s" % session_id)
51
52 def get_iopub_stream(self, session_id):
53 return self.get_stream(session_id, 'iopub_stream')
54
55 def get_shell_stream(self, session_id):
56 return self.get_stream(session_id, 'shell_stream')
@@ -0,0 +1,80 b''
1 import json
2
3 from tornado import web
4
5 import logging
6
7
8 class ZMQHandler(web.RequestHandler):
9
10 def get_stream(self):
11 """Get the ZMQStream for this request."""
12 raise NotImplementedError('Implement get_stream() in a subclass.')
13
14 def _save_method_args(self, *args, **kwargs):
15 """Save the args and kwargs to get/post/put/delete for future use.
16
17 These arguments are not saved in the request or handler objects, but
18 are often needed by methods such as get_stream().
19 """
20 self._method_args = args
21 self._method_kwargs = kwargs
22
23 def _handle_msgs(self, msg):
24 msgs = [msg]
25 stream = self.get_stream()
26 stream.on_recv(lambda m: msgs.append(json.loads(m)))
27 stream.flush()
28 stream.stop_on_recv()
29 logging.info("Reply: %r" % msgs)
30 self.write(json.dumps(msgs))
31 self.finish()
32
33
34 class ZMQPubHandler(ZMQHandler):
35
36 SUPPORTED_METHODS = ("POST",)
37
38 def post(self, *args, **kwargs):
39 self._save_method_args(*args, **kwargs)
40 try:
41 msg = json.loads(self.request.body)
42 except:
43 self.send_error(status_code=415)
44 else:
45 logging.info("Request: %r" % msg)
46 self.get_stream().send_json(msg)
47
48
49 class ZMQSubHandler(ZMQHandler):
50
51 SUPPORTED_METHODS = ("GET",)
52
53 @web.asynchronous
54 def get(self, *args, **kwargs):
55 self._save_method_args(*args, **kwargs)
56 self.get_stream().on_recv(self._handle_msgs)
57
58
59 class ZMQXReqHandler(ZMQHandler):
60
61 SUPPORTED_METHODS = ("POST",)
62
63 @web.asynchronous
64 def post(self, *args, **kwargs):
65 self._save_method_args(*args, **kwargs)
66 logging.info("request: %r" % self.request)
67 try:
68 msg = json.loads(self.request.body)
69 except:
70 self.send_error(status_code=415)
71 else:
72 logging.info("Reply: %r" % msg)
73 stream = self.get_stream()
74 stream.send_json(msg)
75 stream.on_recv(self._handle_msgs)
76
77
78
79
80 No newline at end of file
@@ -0,0 +1,23 b''
1 """A simple WebSocket to ZMQ forwarder."""
2
3 from tornado import websocket
4
5 class ZMQWebSocketBridge(websocket.WebSocketHandler):
6 """A handler to forward between a WebSocket at ZMQ socket."""
7
8 def open(self):
9 self.stream
10
11 @property
12 def stream(self):
13 raise NotImplementedError("stream property must be implemented in a subclass")
14
15 def on_message(self, message):
16 self.stream.send(message)
17
18 def on_zmq_reply(self, reply_list):
19 for part in reply_list:
20 self.write_message(part)
21
22 def on_close(self):
23 print "WebSocket closed"
@@ -1,38 +1,122 b''
1
1 import json
2 import logging
2 3 import os
4 import uuid
5
6 import zmq
3 7
4 import tornado.httpserver
8 # Install the pyzmq ioloop. This has to be done before anything else from
9 # tornado is imported.
10 from zmq.eventloop.zmqstream import ZMQStream
11 from zmq.eventloop import ioloop
5 12 import tornado.ioloop
6 import tornado.options
7 import tornado.web
13 tornado.ioloop = ioloop
14
15 from tornado import httpserver
16 from tornado import options
17 from tornado import web
18 from tornado import websocket
19
20 from kernelmanager import KernelManager
8 21
9 from tornado.options import define, options
22 options.define("port", default=8888, help="run on the given port", type=int)
10 23
11 define("port", default=8888, help="run on the given port", type=int)
24 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
25 _session_id_regex = r"(?P<session_id>\w+)"
12 26
13 class MainHandler(tornado.web.RequestHandler):
27
28 class MainHandler(web.RequestHandler):
14 29 def get(self):
15 30 self.render('notebook.html')
16 31
17 32
18 class NotebookApplication(tornado.web.Application):
33 class KernelHandler(web.RequestHandler):
34
35 def get(self):
36 self.write(json.dumps(self.application.kernel_manager.kernel_ids))
37
38 def post(self):
39 kid = self.application.kernel_manager.start_kernel()
40 logging.info("Starting kernel: %s" % kid)
41 self.write(json.dumps(kid))
42
43
44 class SessionHandler(web.RequestHandler):
45
46 def post(self, *args, **kwargs):
47 kernel_id = kwargs['kernel_id']
48 session_id = kwargs['session_id']
49 logging.info("Starting session: %s, %s" % (kernel_id,session_id))
50 km = self.application.kernel_manager
51 sm = km.get_session_manager(kernel_id)
52 sm.start_session(session_id)
53 self.finish()
54
55
56 class ZMQStreamHandler(websocket.WebSocketHandler):
57
58 stream_name = ''
59
60 def open(self, *args, **kwargs):
61 kernel_id = kwargs['kernel_id']
62 session_id = kwargs['session_id']
63 logging.info("Connection open: %s, %s" % (kernel_id,session_id))
64 sm = self.application.kernel_manager.get_session_manager(kernel_id)
65 method_name = "get_%s_stream" % self.stream_name
66 method = getattr(sm, method_name)
67 self.zmq_stream = method(session_id)
68 self.zmq_stream.on_recv(self._on_zmq_reply)
69 self.session_manager = sm
70 self.session_id = session_id
71
72 def on_message(self, msg):
73 logging.info("Message received: %r" % msg)
74 self.zmq_stream.send(msg)
75
76 def on_close(self):
77 logging.info("Connection closed: %s, %s" % (kernel_id,session_id))
78 self.zmq_stream.close()
79
80 def _on_zmq_reply(self, msg):
81 logging.info("Message reply: %r" % msg)
82 self.write_message(msg)
83
84
85 class IOPubStreamHandler(ZMQStreamHandler):
86
87 stream_name = 'iopub'
88
89
90 class ShellStreamHandler(ZMQStreamHandler):
91
92 stream_name = 'shell'
93
94
95 class NotebookApplication(web.Application):
96
19 97 def __init__(self):
20 98 handlers = [
21 (r"/", MainHandler)
99 (r"/", MainHandler),
100 (r"/kernels", KernelHandler),
101 (r"/kernels/%s/sessions/%s" % (_kernel_id_regex,_session_id_regex), SessionHandler),
102 (r"/kernels/%s/sessions/%s/iopub" % (_kernel_id_regex,_session_id_regex), IOPubStreamHandler),
103 (r"/kernels/%s/sessions/%s/shell" % (_kernel_id_regex,_session_id_regex), ShellStreamHandler),
22 104 ]
23 105 settings = dict(
24 106 template_path=os.path.join(os.path.dirname(__file__), "templates"),
25 107 static_path=os.path.join(os.path.dirname(__file__), "static"),
26 108 )
27 tornado.web.Application.__init__(self, handlers, **settings)
109 web.Application.__init__(self, handlers, **settings)
110 self.context = zmq.Context()
111 self.kernel_manager = KernelManager(self.context)
28 112
29 113
30 114 def main():
31 tornado.options.parse_command_line()
115 options.parse_command_line()
32 116 application = NotebookApplication()
33 http_server = tornado.httpserver.HTTPServer(application)
34 http_server.listen(options.port)
35 tornado.ioloop.IOLoop.instance().start()
117 http_server = httpserver.HTTPServer(application)
118 http_server.listen(options.options.port)
119 ioloop.IOLoop.instance().start()
36 120
37 121
38 122 if __name__ == "__main__":
@@ -8,10 +8,11 b' var IPYTHON = {};'
8 8
9 9 var Notebook = function (selector) {
10 10 this.element = $(selector);
11 this.element.scroll();
11 12 this.element.data("notebook", this);
12 13 this.next_prompt_number = 1;
13 14 this.bind_events();
14 }
15 };
15 16
16 17
17 18 Notebook.prototype.bind_events = function () {
@@ -197,6 +198,7 b' Notebook.prototype.move_cell_down = function (index) {'
197 198
198 199 Notebook.prototype.sort_cells = function () {
199 200 var ncells = this.ncells();
201 var sindex = this.selected_index();
200 202 var swapped;
201 203 do {
202 204 swapped = false
@@ -209,6 +211,7 b' Notebook.prototype.sort_cells = function () {'
209 211 };
210 212 };
211 213 } while (swapped);
214 this.select(sindex);
212 215 return this;
213 216 };
214 217
@@ -464,7 +467,7 b' TextCell.prototype.select = function () {'
464 467 TextCell.prototype.edit = function () {
465 468 var text_cell = this.element;
466 469 var input = text_cell.find("textarea.text_cell_input");
467 var output = text_cell.find("div.text_cell_render");
470 var output = text_cell.find("div.text_cell_render");
468 471 output.hide();
469 472 input.show().trigger('focus');
470 473 };
@@ -475,6 +478,10 b' TextCell.prototype.render = function () {'
475 478 var input = text_cell.find("textarea.text_cell_input");
476 479 var output = text_cell.find("div.text_cell_render");
477 480 var text = input.val();
481 if (text === "") {
482 text = this.placeholder;
483 input.val(text);
484 };
478 485 output.html(text)
479 486 input.html(text);
480 487 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
@@ -501,6 +508,42 b' TextCell.prototype.config_mathjax = function () {'
501 508 //============================================================================
502 509
503 510
511 var KernelManager = function () {
512 this.kernelid = null;
513 this.baseurl = "/kernels";
514 };
515
516
517 KernelManager.prototype.create_kernel = function () {
518 var that = this;
519 $.post(this.baseurl, function (data) {
520 that.kernelid = data;
521 }, 'json');
522 }
523
524
525 KernelManager.prototype.execute = function (code, callback) {
526 var msg = {
527 header : {msg_id : 0, username : "bgranger", session: 0},
528 msg_type : "execute_request",
529 content : {code : code}
530 };
531 var settings = {
532 data : JSON.stringify(msg),
533 processData : false,
534 contentType : "application/json",
535 success : callback,
536 type : "POST"
537 }
538 var url = this.baseurl + "/" + this.kernelid + "/" + ""
539 }
540
541
542 //============================================================================
543 // On document ready
544 //============================================================================
545
546
504 547 $(document).ready(function () {
505 548
506 549 MathJax.Hub.Config({
General Comments 0
You need to be logged in to leave comments. Login now