NeoSense
Exploits
{"nbformat_minor": 0, "cells": [{"source": "# Run a Web Server in a Notebook\n\nIn this notebook, we show how to run a [Tornado](http://www.tornadoweb.org/en/stable/) or [Flask](http://flask.pocoo.org/) web server within a notebook, and access it from the public Internet. It sounds hacky, but the technique can prove useful:\n\n1. To quickly prototype a REST API for an external web application to consume\n2. To quickly expose a simple web dashboard to select external users\n\nIn this notebook, we'll demonstrate the technique using both Tornado and Flask as the web server. In both cases, the servers will listen for HTTPS connections and use a self-signed certificate. The servers will not authenticate connecting users / clients. (We want to keep things simple for this demo, but such authentication is an obvious next step in securing the web service for real-world use.)\n\n## Define the Demo Scenario\n\nSuppose we have completed a notebook that, among other things, can plot a point-in-time sample of data from an external source. Assume we now want to surface this plot in a very simple UI that has:\n\n1. The title of the demo\n1. The current plot\n2. A refresh button that takes a new sample and updates the plot\n\n## Create the Plotting Function\n\nSuppose we have a function that generates a plot and returns the image as a PNG in a Python string.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 1, "cell_type": "code", "source": "import matplotlib.pyplot as plt\nimport pandas as pd\nimport numpy\nimport io\npd.options.display.mpl_style = 'default'", "outputs": [], "metadata": {"collapsed": true, "trusted": true}}, {"execution_count": 2, "cell_type": "code", "source": "def plot_random_numbers(n=50):\n '''\n Plot random numbers as a line graph.\n '''\n fig, ax = plt.subplots()\n # generate some random numbers\n arr = numpy.random.randn(n)\n ax.plot(arr)\n ax.set_title('Random numbers!')\n # fetch the plot bytes\n output = io.BytesIO()\n plt.savefig(output, format='png')\n png = output.getvalue()\n plt.close()\n return png", "outputs": [], "metadata": {"collapsed": true, "trusted": true}}, {"source": "We can test our function by showing its output inline using the `Image` utility from IPython.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 3, "cell_type": "code", "source": "from IPython.display import Image\nImage(plot_random_numbers())", "outputs": [{"output_type": "stream", "name": "stderr", "text": "/home/notebook/ka_env/lib/python2.7/site-packages/matplotlib/font_manager.py:1282: UserWarning: findfont: Font family [u'monospace'] not found. Falling back to Bitstream Vera Sans\n (prop.get_family(), self.defaultFamily[fontext]))\n"}, {"execution_count": 3, "output_type": "execute_result", "data": {"image/png": "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\nRK5CYII=\n", "text/plain": "<IPython.core.display.Image object>"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "## Create a Simple Dashboard Page\n\nNow we'll craft a simple dashboard page that includes our plot. We don't *have* to do anything fancy here other than use an `<img>` tag and a `<button>`. But to demonstate what's possible, we'll make it pretty with Bootstrap and jQuery, and use a Jinja template that accepts the demo title as a parameter.\n\nNote that the image tag points to a `/plot` resource on the server. Nothing dictates that we must fetch the plot image from our dashboard page. Another application could treat our web server as an API and use it in other ways.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 4, "cell_type": "code", "source": "import jinja2", "outputs": [], "metadata": {"collapsed": true, "trusted": true}}, {"execution_count": 5, "cell_type": "code", "source": "page = jinja2.Template('''\\\n<!doctype html>\n<html>\n <head>\n <link rel=\"stylesheet\" type=\"text/css\" href=\"//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css\" />\n <title>{{ title }}</title>\n </head>\n <body>\n <nav class=\"navbar navbar-default\">\n <div class=\"container-fluid\">\n <div class=\"navbar-header\">\n <a class=\"navbar-brand\" href=\"#\">{{ title }}</a>\n </div>\n </div>\n </nav>\n <div class=\"container text-center\">\n <div class=\"row\">\n <img src=\"/plot\" alt=\"Random numbers for a plot\" />\n </div>\n <div class=\"row\">\n <button class=\"btn btn-primary\">Refresh Plot</button>\n </div>\n </div>\n <script type=\"text/javascript\" src=\"//code.jquery.com/jquery-2.1.3.min.js\"></script>\n <script type=\"text/javascript\">\n console.debug('running');\n $('button').on('click', function() {\n $('img').attr('src', '/plot?'+(new Date().getTime()));\n });\n </script>\n </body>\n</html>''')", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"source": "We can now expose both the plotting function and the template via our web servers (Tornado first, then Flask) using the following endpoints:\n\n* `/` will serve the dashboard HTML.\n* `/plot` will serve the plot PNG.", "cell_type": "markdown", "metadata": {}}, {"source": "## Run Tornado in a Notebook\n\nFirst we create a self-signed certificate using the `openssl` command line library. If we had a real cert, we could use it instead.", "cell_type": "markdown", "metadata": {"collapsed": true}}, {"execution_count": 6, "cell_type": "code", "source": "%%bash\nmkdir -p -m 700 ~/.ssh\nopenssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \\\n -subj \"/C=XX/ST=Unknown/L=Somewhere/O=None/CN=None\" \\\n -keyout /home/notebook/.ssh/notebook.key -out /home/notebook/.ssh/notebook.crt", "outputs": [{"output_type": "stream", "name": "stderr", "text": "Generating a 4096 bit RSA private key\n..................++\n......................++\nwriting new private key to '/home/notebook/.ssh/notebook.key'\n-----\n"}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "Next we import the Tornado models we need.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 7, "cell_type": "code", "source": "import tornado.ioloop\nimport tornado.web\nimport tornado.httpserver", "outputs": [], "metadata": {"collapsed": true, "trusted": true}}, {"source": "Then we define the request handlers for our two endpoints.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 8, "cell_type": "code", "source": "class MainHandler(tornado.web.RequestHandler):\n def get(self):\n '''Renders the template with a title on HTTP GET.'''\n self.finish(page.render(title='Tornado Demo'))", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"execution_count": 9, "cell_type": "code", "source": "class PlotHandler(tornado.web.RequestHandler):\n def get(self):\n '''Creates the plot and returns it on HTTP GET.'''\n self.set_header('content-type', 'image/png')\n png = plot_random_numbers()\n self.finish(png)", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"source": "Now we define the application object which maps the web paths to the handlers.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 10, "cell_type": "code", "source": "application = tornado.web.Application([\n (r\"/\", MainHandler),\n (r\"/plot\", PlotHandler)\n])", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"source": "Finally, we create a new HTTP server bound to a publicly exposed port on our notebook server (e.g., 9000) and using the self-signed certificate with corresponding key.", "cell_type": "markdown", "metadata": {}}, {"source": "<div class=\"alert\">\n <div class=\"row\">\n <div class=\"col-sm-1\"><img src=\"https://knowledgeanyhow.org/static/images/favicon_32x32.png\" style=\"margin-top: -6px\"/></div>\n <div class=\"col-sm-11\">In IBM Knowledge Anyhow Workbench, ports 9000 through 9004 are exposed on a public interface. We can bind our webserver to any of those ports.</div>\n </div>\n</div>", "cell_type": "markdown", "metadata": {}}, {"execution_count": 11, "cell_type": "code", "source": "server = tornado.httpserver.HTTPServer(application, ssl_options = {\n \"certfile\": '/home/notebook/.ssh/notebook.crt',\n \"keyfile\": '/home/notebook/.ssh/notebook.key'\n})\nserver.listen(9000, '0.0.0.0')", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"source": "To see the result, we need to visit the public IP address of our notebook server. For example, if our IP address is 192.168.11.10, we would visit https://192.168.11.10:9000.", "cell_type": "markdown", "metadata": {}}, {"source": "<div class=\"alert\">\n<div class=\"row\">\n <div class=\"col-sm-1\"><img src=\"https://knowledgeanyhow.org/static/images/favicon_32x32.png\" style=\"margin-top: -6px\"/></div>\n <div class=\"col-sm-11\">In IBM Knowledge Anyhow Workbench, we can get our public IP address from an environment variable by executing the code below in our notebook:\n<pre style=\"background-color: transparent\">import os\nos.getenv('HOST_PUBLIC_IP')</pre>\n </div>\n</div>\n</div>", "cell_type": "markdown", "metadata": {}}, {"source": "When we visit the web server in a browser and accept the self-signed cert warning, we should see the resulting dashboard. Clicking *Refresh Plot* in the dashboard shows us a new plot. \n\nNote that since IPython itself is based on Tornado, we are able to run other cells and get ouput while the web server is running. In fact, we can even modify the plotting function and template and see the changes the next time we refresh the dashboard in our browser.\n\nWhen we want to shut the server down, we execute the lines below. Restarting the notebook kernel has the same net effect.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 12, "cell_type": "code", "source": "server.close_all_connections()\nserver.stop()", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"source": "## Run Flask in a Notebook\n\nThe same technique works with Flask, albeit with different pros and cons. First, we need to install Flask since it does not come preinstalled in the notebook environment by default.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 13, "cell_type": "code", "source": "!pip install flask", "outputs": [{"output_type": "stream", "name": "stdout", "text": "Requirement already satisfied (use --upgrade to upgrade): flask in /home/notebook/ka_env/lib/python2.7/site-packages\r\nRequirement already satisfied (use --upgrade to upgrade): Werkzeug>=0.7 in /home/notebook/ka_env/lib/python2.7/site-packages (from flask)\r\nRequirement already satisfied (use --upgrade to upgrade): Jinja2>=2.4 in /home/notebook/ka_env/lib/python2.7/site-packages (from flask)\r\nRequirement already satisfied (use --upgrade to upgrade): itsdangerous>=0.21 in /home/notebook/ka_env/lib/python2.7/site-packages (from flask)\r\nRequirement already satisfied (use --upgrade to upgrade): markupsafe in /usr/local/lib/python2.7/dist-packages (from Jinja2>=2.4->flask)\r\nCleaning up...\r\n"}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "Now we import our Flask requirements, define our app, and create our route mappings.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 14, "cell_type": "code", "source": "from flask import Flask, make_response", "outputs": [], "metadata": {"collapsed": true, "trusted": true}}, {"execution_count": 15, "cell_type": "code", "source": "flask_app = Flask('flask_demo')", "outputs": [], "metadata": {"collapsed": true, "trusted": true}}, {"execution_count": 16, "cell_type": "code", "source": "@flask_app.route('/')\ndef index():\n '''Renders the template with a title on HTTP GET.'''\n return page.render(title='Flask Demo')", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"execution_count": 17, "cell_type": "code", "source": "@flask_app.route('/plot')\ndef get_plot():\n '''Creates the plot and returns it on HTTP GET.'''\n response = make_response(plot_random_numbers())\n response.mimetype = 'image/png'\n return response", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"source": "Finally, we run the Flask web server. Flask supports the generation of an ad-hoc HTTP certificate and key so we don't need to explicitly put one on disk like we did in the case of Tornado.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 18, "cell_type": "code", "source": "flask_app.run(host='0.0.0.0', port=9000, ssl_context='adhoc')", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"source": "Unlike in the Tornado case, the run command above blocks the notebook kernel from returning for as long as the web server is running. To stop the server, we need to interrupt the kernel (*Kernel* → *Interrupt*). \n\n## Run Flask in a Tornado WSGIContainer\n\nIf we are in love with Flask syntax, but miss the cool, non-blocking ability of Tornado, we can run the Flask application in a Tornado WSGIContainer like so.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 21, "cell_type": "code", "source": "from tornado.wsgi import WSGIContainer\nserver = tornado.httpserver.HTTPServer(WSGIContainer(flask_app), ssl_options = {\n \"certfile\": '/home/notebook/.ssh/notebook.crt',\n \"keyfile\": '/home/notebook/.ssh/notebook.key'\n})\nserver.listen(9000, '0.0.0.0')", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"source": "And once we do, we can view the dashboard in a web browser even while executing cells in the notebook. When we're done, we can cleanup with the same logic as in the pure Tornado case.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 22, "cell_type": "code", "source": "server.close_all_connections()\nserver.stop()", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"source": "## Conclusion\n\nIn this notebook, we:\n\n* Defined a simple function that returns a PNG of a plot\n* Defined a template that renders a very simple HTML dashboard\n* Exposed two HTTPS endpoints in Tornado, one for the dashboard HTML and one for the plot\n* Exposed two HTTPS endpoints in Flask for the same resources\n* Exposed two HTTPS endpoints in Flask + Tornado for the same resources\n\nOf course, what we chose to expose was specific to the demo scenario. For example, we could have just as easily created a REST API that accepted feature values for classification and feedback about whether the classification was right or not for future training.\n\nWhile the result is not \"production ready\", it does allow us to expose prototype code to other users without worrying about migrating our work from notebook(s) to other environments.", "cell_type": "markdown", "metadata": {}}, {"source": "<div class=\"alert\">\n<div class=\"row\">\n <div class=\"col-sm-1\"><img src=\"https://knowledgeanyhow.org/static/images/favicon_32x32.png\" style=\"margin-top: -6px\"/></div>\n <div class=\"col-sm-11\">This notebook was created using <a href=\"https://knowledgeanyhow.org\">IBM Knowledge Anyhow Workbench</a>. To learn more, visit us at <a href=\"https://knowledgeanyhow.org\">https://knowledgeanyhow.org</a>.</div>\n </div>\n</div>", "cell_type": "markdown", "metadata": {"collapsed": true}}], "nbformat": 4, "metadata": {"kernelspec": {"display_name": "Python 2", "name": "python2", "language": "python"}, "language_info": {"mimetype": "text/x-python", "nbconvert_exporter": "python", "version": "2.7.6", "name": "python", "file_extension": ".py", "pygments_lexer": "ipython2", "codemirror_mode": {"version": 2, "name": "ipython"}}}}
↑