@Gopi_M
The following is a very basic example to illustrate using nginx with a Flask app under gunicorn control which embeds a bokeh server document.
DISCLAIMER This example uses an HTTP server only (no HTTPS protocols, SSL keys/certificates, etc.) that I was using quite a while ago to get a handle on integration. I now do everything via Heroku and its nginx buildpack to host online and have HTTPS, etc.
With that background, the relevant pieces are the nginx configuration file (nginx.conf), the gunicorn configuration file (gunicorn_config.py), and the flask app (flask_gunicorn_embed.py). The flask app is a very minor modification of the official bokeh flask+gunicorn embed example to leverage the server and port variables I have defined, but otherwise functionally the same. I have not included the supporting theme and templates required to run the example for brevity.
The server is invoked via …
gunicorn --config gunicorn_config.py flask_gunicorn_embed:app
And for the server on the local network, a client accesses via the following in the browser search bar. Change the nginx server parameters as required for your network topology.
http://192.168.0.135:8080/
nginx.conf
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 8080;
server_name 192.168.0.135;
#charset koi8-r;
#access_log logs/host.access.log main;
#location / {
# root html;
# index index.html index.htm;
#}
location / {
proxy_pass http://127.0.0.1:5100;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host:$server_port;
proxy_buffering off;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
include servers/*;
}
gunicorn_config.py
import multiprocessing
import urllib.parse
bind = "127.0.0.1:5100"
workers = multiprocessing.cpu_count() * 2 + 1
pr = urllib.parse.urlparse('//'+bind)
APP_SERVER_ADDR = pr.hostname
APP_SERVER_PORT = pr.port
NGINX_SERVER_ADDR = "192.168.0.135"
NGINX_SERVER_PORT = 8080
flask_gunicorn_embed.py
try:
import asyncio
except ImportError:
raise RuntimeError("This example requries Python3 / asyncio")
from threading import Thread
from flask import Flask, render_template
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from bokeh.application import Application
from bokeh.application.handlers import FunctionHandler
from bokeh.embed import server_document
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, Slider
from bokeh.plotting import figure
from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature
from bokeh.server.server import BaseServer
from bokeh.server.tornado import BokehTornado
from bokeh.server.util import bind_sockets
from bokeh.themes import Theme
from gunicorn_config import APP_SERVER_ADDR, APP_SERVER_PORT
from gunicorn_config import NGINX_SERVER_ADDR, NGINX_SERVER_PORT
if __name__ == '__main__':
print('This script is intended to be run with gunicorn. e.g.')
print()
print(' gunicorn -w 4 flask_gunicorn_embed:app')
print()
print('will start the app on four processes')
import sys
sys.exit()
app = Flask(__name__)
def bkapp(doc):
df = sea_surface_temperature.copy()
source = ColumnDataSource(data=df)
plot = figure(x_axis_type='datetime', y_range=(0, 25), y_axis_label='Temperature (Celsius)',
title="Sea Surface Temperature at 43.18, -70.43")
plot.line('time', 'temperature', source=source)
def callback(attr, old, new):
if new == 0:
data = df
else:
data = df.rolling('{0}D'.format(new)).mean()
source.data = ColumnDataSource.from_df(data)
slider = Slider(start=0, end=30, value=0, step=1, title="Smoothing by N Days")
slider.on_change('value', callback)
doc.add_root(column(slider, plot))
doc.theme = Theme(filename="theme.yaml")
# can't use shortcuts here, since we are passing to low level BokehTornado
bkapp = Application(FunctionHandler(bkapp))
# This is so that if this app is run using something like "gunicorn -w 4" then
# each process will listen on its own port
sockets, port = bind_sockets(APP_SERVER_ADDR, 0)
@app.route('/', methods=['GET'])
def bkapp_page():
script = server_document('http://%s:%d/bkapp' % (APP_SERVER_ADDR,port))
return render_template("embed.html", script=script, template="Flask")
def bk_worker():
asyncio.set_event_loop(asyncio.new_event_loop())
bokeh_tornado = BokehTornado({'/bkapp': bkapp}, extra_websocket_origins=["localhost:8000", "%s:%d" % (NGINX_SERVER_ADDR,NGINX_SERVER_PORT)])
bokeh_http = HTTPServer(bokeh_tornado)
bokeh_http.add_sockets(sockets)
server = BaseServer(IOLoop.current(), bokeh_tornado, bokeh_http)
server.start()
server.io_loop.start()
t = Thread(target=bk_worker)
t.daemon = True
t.start()