提示用户输入一段代码,当用户输入以后执行。这种模式经常被称为REPL(交互式开发环境),或者Read-Eval-Print-Loop(读取﹣求值﹣输出循环).jupyter notebook就是这样一种在web端的交互式开发环境,如下:
对应.ipynb文件的内容:
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"pi is rough3.142608\n"
]
}
],
"source": [
"import scala.math.random\n",
"import org.apache.spark._\n",
"val slices = 10\n",
"val n = math.min(100000L * slices, Int.MaxValue).toInt\n",
"val count = sc.parallelize(1 until n, slices).map{ i =>\n",
" val x = random*2 - 1\n",
" val y = random*2 -1\n",
" if (x*x + y*y <1) 1 else 0\n",
" }.reduce(_+_)\n",
"println(\"pi is rough\" + 4.0*count/n)"
]
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Apache Toree - Scala",
"language": "scala",
"name": "apache_toree_scala"
},
"language_info": {
"name": "scala",
"version": "2.11.8"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
IPython 内核 所有其它接口,包括Notebook,Qt控制台,ipython控制台和其它第三方接口,都使用IPython内核。IPython内核是一个独立的进程,负责执行用户代码和其它事情,例如计算可能的补全。前端处理器,例如notebook和Qt控制台,使用ZeroMQ传输JSON消息与IPython内核通信,前端处理器与IPython内核通信使用的协议详细描述请参考Jupyter 消息
Notebook 前端处理器做一些额外的事情。除了运行你的代码,它还储存代码和输出、以及markdown注释在一个可编辑的文档中,我们称这个文档为一个notebook。当你保存这个文档时,它会从你的浏览器发送到notebook服务器,服务器将文档保存为.ipynb为拓展名的JSON格式文件,notebook服务器而不是内核,负责保存和载入notebook,因此你可以编辑notebook即使你没有那种编程语言的内核,你仅仅不能运行notebook中的代码。内核不知道notebook文档任何事情,它只是在用户运行代码时获取用户发送的代码并执行。导出notebooks到其他格式 Jupyter中的工具Nbconvert可以将notebook文件转换到其它格式,例如HTML,LaTex.
使用nbconvert和HTML导出器。当你输入一个网址,它从输入的网址获取notebook,然后将其转换为HTML格式,并将HTML呈现给你。
Python Notebook中输入的代码经由浏览器发送给Web服务器,再由Web服务器发送消息到IPython的Kernel执行代码,在Kernel中执行代码所产生的输出会再发送给Web服务器从而发送给浏览器,完成整个运行过程。Web服务器和Kernel之间采用ZeroMQ进行通信。下面为其通信的示意图:
图中,Kernel经由绿色的DEAL-ROUTER通道接收来自Web服务器的命令消息,并返回应答消息。通过红色的PUB-SUB通道传输被执行代码所产生的输出信息。
在Kernel中,用户代码在一个用户环境(字典)中执行,通常无法获得关于Kernel的信息。但是由于用户代码和Kernel在同一进程中执行,因此我们可以通过一些特殊的代码研究Kernel是如何接收、运行并返回消息的。
两种思路:
一、从websocket入手
打开一个新的notebook: http://127.0.0.1:5000/notebooks/Untitled1.ipynb
从chrome调试面板的Network可以看到,有个websocket:ws://127.0.0.1:5000/api/kernels/d13a50b0-6baa-4d5e-8564-95f224daxxxx/channels?session_id=F552491A7C0448A2B5567DE1A71Cxxxx,代码经由它往后端发送,也经由它接收后台返回的信息
当我们运行上头的print("hello world")时,往后台发送如下数据
{
"header":
{
"msg_id":"D8FA79DCD7D140DF8F9C37EE15D9FD6D",
"username":"username",
"session":"91608A811CAE4FA6A5E09D37AF68DF32",
"msg_type":"execute_request",
"version":"5.0"
},
"metadata":{},
"content":
{
"code":"print(\"hello world\")",
"silent":false,
"store_history":true,
"user_expressions":{},
"allow_stdin":true,
"stop_on_error":true
},
"buffers":[],
"parent_header":{},
"channel":"shell"
}
接下来有5个frames:
{
"parent_header":
{
"username": "username",
"session": "91608A811CAE4FA6A5E09D37AF68DF32",
"version": "5.0",
"msg_id": "D8FA79DCD7D140DF8F9C37EE15D9FD6D",
"msg_type": "execute_request"
},
"msg_type": "status",
"msg_id": "ce11828e-1c2a-4254-962f-2db9e2ff3353",
"content":
{
"execution_state": "busy"
},
"header":
{
"username": "root",
"version": "5.0",
"msg_type": "status",
"msg_id": "ce11828e-1c2a-4254-962f-2db9e2ff3353",
"session": "604a11a4-14eb-4f8a-b32d-044d4c195bf3",
"date": "2017-12-03T20:05:06.816757"
},
"channel": "iopub",
"buffers": [],
"metadata":
{
"timestamp": "1512302706811"
}
}
{
"parent_header":
{
"username": "username",
"session": "91608A811CAE4FA6A5E09D37AF68DF32",
"version": "5.0",
"msg_id": "D8FA79DCD7D140DF8F9C37EE15D9FD6D",
"msg_type": "execute_request"
},
"msg_type": "execute_input",
"msg_id": "116696e7-befa-40c7-98c1-88e32e1e9b63",
"content":
{
"execution_count": 1,
"code": "print(\"hello world\")"
},
"header":
{
"username": "root",
"version": "5.0",
"msg_type": "execute_input",
"msg_id": "116696e7-befa-40c7-98c1-88e32e1e9b63",
"session": "604a11a4-14eb-4f8a-b32d-044d4c195bf3",
"date": "2017-12-03T20:05:06.829865"
},
"channel": "iopub",
"buffers": [],
"metadata":
{
"timestamp": "1512302706826"
}
}
{
"parent_header":
{
"username": "username",
"session": "91608A811CAE4FA6A5E09D37AF68DF32",
"version": "5.0", "msg_id": "D8FA79DCD7D140DF8F9C37EE15D9FD6D",
"msg_type": "execute_request"
},
"msg_type": "stream",
"msg_id": "d3c9ffa1-108f-4ea2-88db-100985fe23d0",
"content":
{
"text": "hello world", "name": "stdout"
},
"header":
{
"username": "root",
"version": "5.0",
"msg_type": "stream",
"msg_id": "d3c9ffa1-108f-4ea2-88db-100985fe23d0",
"session": "604a11a4-14eb-4f8a-b32d-044d4c195bf3",
"date": "2017-12-03T20:05:07.071811"
},
"channel": "iopub",
"buffers": [],
"metadata":
{
"timestamp": "1512302707065"
}
}
{
"parent_header":
{
"username": "username",
"session": "91608A811CAE4FA6A5E09D37AF68DF32",
"version": "5.0",
"msg_id": "D8FA79DCD7D140DF8F9C37EE15D9FD6D",
"msg_type": "execute_request"
},
"msg_type": "execute_reply",
"msg_id": "6ee99d0d-9c92-41ce-be06-39241719f321",
"content":
{
"status": "ok",
"execution_count": 1,
"payload": [],
"user_expressions": {}
},
"header":
{
"username": "root",
"version": "5.0",
"msg_type": "execute_reply",
"msg_id": "6ee99d0d-9c92-41ce-be06-39241719f321",
"session": "604a11a4-14eb-4f8a-b32d-044d4c195bf3",
"date": "2017-12-03T20:05:07.097685"
},
"channel": "shell",
"buffers": [],
"metadata":
{
"timestamp": "1512302707094"
}
}
{
"parent_header":
{
"username": "username",
"session": "91608A811CAE4FA6A5E09D37AF68DF32",
"version": "5.0",
"msg_id": "D8FA79DCD7D140DF8F9C37EE15D9FD6D",
"msg_type": "execute_request"
},
"msg_type": "status",
"msg_id": "32857f80-db87-4775-9350-fe009a8e9c18",
"content": {
"execution_state": "idle"
},
"header":
{
"username": "root",
"version": "5.0",
"msg_type": "status",
"msg_id": "32857f80-db87-4775-9350-fe009a8e9c18",
"session": "604a11a4-14eb-4f8a-b32d-044d4c195bf3",
"date": "2017-12-03T20:05:07.098384"
},
"channel": "iopub",
"buffers": [],
"metadata":
{
"timestamp": "1512302707095"
}
}
在支持WebSocket的浏览器中,在创建socket之后。可以通过onopen,onmessage,onclose和onerror四个事件实现对socket进行响应
一个简单的 示例
var ws = new WebSocket(“ws://localhost:8080”);
ws.onopen = function()
{ console.log(“open”);
ws.send(“hello”);
};
ws.onmessage = function(evt)
{
console.log(evt.data)
};
ws.onclose = function(evt)
{
console.log(“WebSocketClosed!”);
};
ws.onerror = function(evt)
{
console.log(“WebSocketError!”);
};
1.var ws = new WebSocket(“ws://localhost:8080”);
申请一个WebSocket对象,参数是需要连接的服务器端的地址,同http协议使用http://开头一样,WebSocket协议的URL使用ws://开头,另外安全的WebSocket协议使用wss://开头。
ws.send(“hello”);
用于将消息发送到服务端
2.ws.onopen = function() { console.log(“open”)};
当websocket创建成功时,即会触发onopen事件
3.ws.onmessage = function(evt) { console.log(evt.data) };
当客户端收到服务端发来的消息时,会触发onmessage事件,参数evt.data中包含server传输过来的数据
4.ws.onclose = function(evt) { console.log(“WebSocketClosed!”); };
当客户端收到服务端发送的关闭连接的请求时,触发onclose事件
5.ws.onerror = function(evt) { console.log(“WebSocketError!”); };
如果出现连接,处理,接收,发送数据失败的时候就会触发onerror事件
我们可以看出所有的操作都是采用事件的方式触发的,这样就不会阻塞UI,使得UI有更快的响应时间,得到更好的用户体验。
二、从页面入手
在stack overflow里找到解答:
var handle_output = function (data) {console.log(data);}
//callbacks.iopub.output is used to get the data from execute
var callbacks = {
iopub : {output : handle_output,}
}
//(read the source F12->static/notebook/js/services/kernels/kernel.js)
//kernel.js/717th lines Kernel.prototype.execute
var kernel = IPython.notebook.kernel;
kernel.execute("print('hello')",callbacks)
kernel.js的function:
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
define('services/kernels/kernel',[
'jquery',
'base/js/utils',
'./comm',
'./serialize',
'base/js/events'
], function($, utils, comm, serialize, events) {
"use strict";
/**
* A Kernel class to communicate with the Python kernel. This
* should generally not be constructed directly, but be created
* by. the `Session` object. Once created, this object should be
* used to communicate with the kernel.
*
* Preliminary documentation for the REST API is at
* https://github.com/ipython/ipython/wiki/IPEP-16%3A-Notebook-multi-directory-dashboard-and-URL-mapping#kernels-api
*
* @class Kernel
* @param {string} kernel_service_url - the URL to access the kernel REST api
* @param {string} ws_url - the websockets URL
* @param {string} name - the kernel type (e.g. python3)
*/
var Kernel = function (kernel_service_url, ws_url, name) {
this.events = events;
this.id = null;
this.name = name;
this.ws = null;
this.kernel_service_url = kernel_service_url;
this.kernel_url = null;
this.ws_url = ws_url || utils.get_body_data("wsUrl");
if (!this.ws_url) {
// trailing 's' in https will become wss for secure web sockets
this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
}
this.username = "username";
this.session_id = utils.uuid();
this._msg_callbacks = {};
this._msg_queue = Promise.resolve();
this.info_reply = {}; // kernel_info_reply stored here after starting
if (typeof(WebSocket) !== 'undefined') {
this.WebSocket = WebSocket;
} else if (typeof(MozWebSocket) !== 'undefined') {
this.WebSocket = MozWebSocket;
} else {
alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox ≥ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
}
this.bind_events();
this.init_iopub_handlers();
this.comm_manager = new comm.CommManager(this);
this.last_msg_id = null;
this.last_msg_callbacks = {};
this._autorestart_attempt = 0;
this._reconnect_attempt = 0;
this.reconnect_limit = 7;
this._pending_messages = [];
};
/**
* @function _get_msg
*/
Kernel.prototype._get_msg = function (msg_type, content, metadata, buffers) {
return msg;
};
/**
* @function bind_events
*/
Kernel.prototype.bind_events = function () {
};
/**
* Initialize the iopub handlers.
*
* @function init_iopub_handlers
*/
Kernel.prototype.init_iopub_handlers = function () {
};
/**
* GET /api/kernels
*
* Get the list of running kernels.
*
* @function list
* @param {function} [success] - function executed on ajax success
* @param {function} [error] - functon executed on ajax error
*/
Kernel.prototype.list = function (success, error) {
};
/**
* POST /api/kernels
*
* Start a new kernel.
*
* In general this shouldn't be used -- the kernel should be
* started through the session API. If you use this function and
* are also using the session API then your session and kernel
* WILL be out of sync!
*
* @function start
* @param {params} [Object] - parameters to include in the query string
* @param {function} [success] - function executed on ajax success
* @param {function} [error] - functon executed on ajax error
*/
Kernel.prototype.start = function (params, success, error) {
return url;
};
/**
* GET /api/kernels/[:kernel_id]
*
* Get information about the kernel.
*
* @function get_info
* @param {function} [success] - function executed on ajax success
* @param {function} [error] - functon executed on ajax error
*/
Kernel.prototype.get_info = function (success, error) {
};
/**
* DELETE /api/kernels/[:kernel_id]
*
* Shutdown the kernel.
*
* If you are also using sessions, then this function shoul NOT be
* used. Instead, use Session.delete. Otherwise, the session and
* kernel WILL be out of sync.
*
* @function kill
* @param {function} [success] - function executed on ajax success
* @param {function} [error] - functon executed on ajax error
*/
Kernel.prototype.kill = function (success, error) {
};
/**
* POST /api/kernels/[:kernel_id]/interrupt
*
* Interrupt the kernel.
*
* @function interrupt
* @param {function} [success] - function executed on ajax success
* @param {function} [error] - functon executed on ajax error
*/
Kernel.prototype.interrupt = function (success, error) {
};
Kernel.prototype.restart = function (success, error) {
/**
* POST /api/kernels/[:kernel_id]/restart
*
* Restart the kernel.
*
* @function interrupt
* @param {function} [success] - function executed on ajax success
* @param {function} [error] - functon executed on ajax error
*/
};
Kernel.prototype.reconnect = function () {
};
Kernel.prototype._on_success = function (success) {
/**
* Handle a successful AJAX request by updating the kernel id and
* name from the response, and then optionally calling a provided
* callback.
*
* @function _on_success
* @param {function} success - callback
*/
};
Kernel.prototype._on_error = function (error) {
/**
* Handle a failed AJAX request by logging the error message, and
* then optionally calling a provided callback.
*
* @function _on_error
* @param {function} error - callback
*/
};
Kernel.prototype._kernel_created = function (data) {
/**
* Perform necessary tasks once the kernel has been started,
* including actually connecting to the kernel.
*
* @function _kernel_created
* @param {Object} data - information about the kernel including id
*/
};
Kernel.prototype._kernel_connected = function () {
/**
* Perform necessary tasks once the connection to the kernel has
* been established. This includes requesting information about
* the kernel.
*
* @function _kernel_connected
*/
};
Kernel.prototype._kernel_dead = function () {
/**
* Perform necessary tasks after the kernel has died. This closing
* communication channels to the kernel if they are still somehow
* open.
*
* @function _kernel_dead
*/
};
Kernel.prototype.start_channels = function () {
/**
* Start the websocket channels.
* Will stop and restart them if they already exist.
*
* @function start_channels
*/
};
Kernel.prototype._ws_opened = function (evt) {
/**
* Handle a websocket entering the open state,
* signaling that the kernel is connected when websocket is open.
*
* @function _ws_opened
*/
};
Kernel.prototype._ws_closed = function(ws_url, error) {
/**
* Handle a websocket entering the closed state. If the websocket
* was not closed due to an error, try to reconnect to the kernel.
*
* @function _ws_closed
* @param {string} ws_url - the websocket url
* @param {bool} error - whether the connection was closed due to an error
*/
};
Kernel.prototype._schedule_reconnect = function () {
/**
* function to call when kernel connection is lost
* schedules reconnect, or fires 'connection_dead' if reconnect limit is hit
*/
};
Kernel.prototype.stop_channels = function () {
/**
* Close the websocket. After successful close, the value
* in `this.ws` will be null.
*
* @function stop_channels
*/
};
Kernel.prototype.is_connected = function () {
/**
* Check whether there is a connection to the kernel. This
* function only returns true if websocket has been
* created and has a state of WebSocket.OPEN.
*
* @function is_connected
* @returns {bool} - whether there is a connection
*/
// if any channel is not ready, then we're not connected
};
Kernel.prototype.is_fully_disconnected = function () {
/**
* Check whether the connection to the kernel has been completely
* severed. This function only returns true if all channel objects
* are null.
*
* @function is_fully_disconnected
* @returns {bool} - whether the kernel is fully disconnected
*/
return (this.ws === null);
};
Kernel.prototype._send = function(msg) {
/**
* Send a message (if the kernel is connected) or queue the message for future delivery
*
* Pending messages will automatically be sent when a kernel becomes connected.
*
* @function _send
* @param msg
*/
}
Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) {
/**
* Send a message on the Kernel's shell channel
*
* If the kernel is not connected, the message will be buffered.
*
* @function send_shell_message
*/
return msg.header.msg_id;
};
Kernel.prototype.kernel_info = function (callback) {
/**
* Get kernel info
*
* @function kernel_info
* @param callback {function}
*
* When calling this method, pass a callback function that expects one argument.
* The callback will be passed the complete `kernel_info_reply` message documented
* [here](https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-info)
*/
};
Kernel.prototype.comm_info = function (target_name, callback) {
/**
* Get comm info
*
* @function comm_info
* @param callback {function}
*
* When calling this method, pass a callback function that expects one argument.
* The callback will be passed the complete `comm_info_reply` message documented
* [here](https://jupyter-client.readthedocs.io/en/latest/messaging.html#comm_info)
*/
};
Kernel.prototype.inspect = function (code, cursor_pos, callback) {
/**
* Get info on an object
*
* When calling this method, pass a callback function that expects one argument.
* The callback will be passed the complete `inspect_reply` message documented
* [here](https://jupyter-client.readthedocs.io/en/latest/messaging.html#object-information)
*
* @function inspect
* @param code {string}
* @param cursor_pos {integer}
* @param callback {function}
*/
};
Kernel.prototype.execute = function (code, callbacks, options) {
/**
* Execute given code into kernel, and pass result to callback.
*
* @async
* @function execute
* @param {string} code
* @param [callbacks] {Object} With the following keys (all optional)
* @param callbacks.shell.reply {function}
* @param callbacks.shell.payload.[payload_name] {function}
* @param callbacks.iopub.output {function}
* @param callbacks.iopub.clear_output {function}
* @param callbacks.input {function}
* @param {object} [options]
* @param [options.silent=false] {Boolean}
* @param [options.user_expressions=empty_dict] {Dict}
* @param [options.allow_stdin=false] {Boolean} true|false
*
* @example
*
* The options object should contain the options for the execute
* call. Its default values are:
*
* options = {
* silent : true,
* user_expressions : {},
* allow_stdin : false
* }
*
* When calling this method pass a callbacks structure of the
* form:
*
* callbacks = {
* shell : {
* reply : execute_reply_callback,
* payload : {
* set_next_input : set_next_input_callback,
* }
* },
* iopub : {
* output : output_callback,
* clear_output : clear_output_callback,
* },
* input : raw_input_callback
* }
*
* Each callback will be passed the entire message as a single
* arugment. Payload handlers will be passed the corresponding
* payload and the execute_reply message.
*/
return this.send_shell_message("execute_request", content, callbacks);
};
/**
* When calling this method, pass a function to be called with the
* `complete_reply` message as its only argument when it arrives.
*
* `complete_reply` is documented
* [here](https://jupyter-client.readthedocs.io/en/latest/messaging.html#complete)
*
* @function complete
* @param code {string}
* @param cursor_pos {integer}
* @param callback {function}
*/
Kernel.prototype.complete = function (code, cursor_pos, callback) {
return this.send_shell_message("complete_request", content, callbacks);
};
/**
* @function send_input_reply
*/
Kernel.prototype.send_input_reply = function (input) {
return msg.header.msg_id;
};
/**
* @function register_iopub_handler
*/
Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
this._iopub_handlers[msg_type] = callback;
};
/**
* Get the iopub handler for a specific message type.
*
* @function get_iopub_handler
*/
Kernel.prototype.get_iopub_handler = function (msg_type) {
return this._iopub_handlers[msg_type];
};
/**
* Get callbacks for a specific message.
*
* @function get_callbacks_for_msg
*/
Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
};
/**
* Clear callbacks for a specific message.
*
* @function clear_callbacks_for_msg
*/
Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
if (this._msg_callbacks[msg_id] !== undefined ) {
delete this._msg_callbacks[msg_id];
}
};
/**
* @function _finish_shell
*/
Kernel.prototype._finish_shell = function (msg_id) {
};
/**
* @function _finish_iopub
*/
Kernel.prototype._finish_iopub = function (msg_id) {
};
/**
* Set callbacks for a particular message.
* Callbacks should be a struct of the following form:
* shell : {
*
* }
*
* @function set_callbacks_for_msg
*/
Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
};
Kernel.prototype._handle_ws_message = function (e) {
};
Kernel.prototype._finish_ws_message = function (msg) {
};
Kernel.prototype._handle_shell_reply = function (reply) {
return promise;
};
/**
* @function _handle_payloads
*/
Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
return Promise.all(promise);
};
/**
* @function _handle_status_message
*/
Kernel.prototype._handle_status_message = function (msg) {
};
/**
* Handle clear_output message
*
* @function _handle_clear_output
*/
Kernel.prototype._handle_clear_output = function (msg) {
};
/**
* handle an output message (execute_result, display_data, etc.)
*
* @function _handle_output_message
*/
Kernel.prototype._handle_output_message = function (msg) {
};
/**
* Handle an input message (execute_input).
*
* @function _handle_input message
*/
Kernel.prototype._handle_input_message = function (msg) {
};
/**
* Dispatch IOPub messages to respective handlers. Each message
* type should have a handler.
*
* @function _handle_iopub_message
*/
Kernel.prototype._handle_iopub_message = function (msg) {
};
/**
* @function _handle_input_request
*/
Kernel.prototype._handle_input_request = function (request) {
};
return {'Kernel': Kernel};
});
参考文章:
http://blog.just4fun.site/jupyter-notebook-architecture.html
http://blog.just4fun.site/jupyter-notebook-architecture-hack.html
https://www.tuicool.com/articles/naqIza
http://flamepeak.com/2016/09/12/Jupyter-official-docs-translate-20160912/
https://jupyter-client.readthedocs.io/en/latest/messaging.html#messaging
http://jupyter-notebook.readthedocs.io/en/stable/comms.html
http://hyry.dip.jp/tech/slice/slice.html/36
http://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Connecting%20with%20the%20Qt%20Console.html?highlight=messaging#The-Frontend/Kernel-Model
https://github.com/junjunwudi/zmq-pykernel
https://media.readthedocs.org/pdf/jupyter-notebook/4.x/jupyter-notebook.pdf
https://github.com/ipython/ipython/wiki/IPEP-16%3A-Notebook-multi-directory-dashboard-and-URL-mapping#create_new_notebook
https://stackoverflow.com/questions/26435653/how-do-i-embed-an-ipython-notebook-in-an-iframe-new
http://www.tornadoweb.org/en/stable/web.html#request-handlers
http://jupyter-client.readthedocs.io/en/latest/messaging.html#custom-messages
http://jupyter-notebook.readthedocs.io/en/latest/extending/handlers.html
http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml
http://Check.torproject.org
https://gist.github.com/disarticulate/d06069ff3e71cf828e5329beab8cb084
https://stackoverflow.com/questions/31357718/ipython-javascript-client-api/42418784#42418784
https://www.cnblogs.com/lxtblogs/p/4947898.html
http://jupyter-client.readthedocs.io/en/latest/messaging.html
http://jupyter-notebook.readthedocs.io/en/latest/frontend_config.html#configuring-the-notebook-frontend
http://ipython.org/ipython-doc/stable/development/messaging.html