Hi,
I have a Flask plus Bokeh server setup where the two servers are started by their own scripts (python flask_app.py
and bokeh serve ...
) and the Bokeh plots are embedded in pages by templates using server_document
. In case there is something wrong with the Bokeh server I would like to capture any page error from the Bokeh server by showing a Flask error template. I have tried to add routes like @app.errorhandler(403)
but also @app.teardown_request
in order to try to capture any page error but so far no luck. If I understand correctly as long as flask is able to create the embedded template (the html doc) then that would not return any page error.
To show what I mean I have taken the Flask embed example from git (bokeh/examples/embed/arguments at branch-2.2 · bokeh/bokeh · GitHub) and used that. In the flask_server.py
script I have added some error handling routes but they are not run if Bokeh server reports a eg 403 GET error.
bokeh_server.py
#!/usr/bin/env python
'''This example demonstrates embedding an autoloaded Bokeh server
into a simple Flask application, and passing arguments to Bokeh.
To view the example, run:
python flask_server.py
in this directory, and navigate to:
http://localhost:5000
'''
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, Slider, TextInput
from bokeh.plotting import figure
# Retrieving the arguments
args = curdoc().session_context.request.arguments
try:
batchid = int(args.get('batchid')[0])
except (ValueError, TypeError):
batchid = 1
func = {
1 : np.cos,
2 : np.sin,
3 : np.tan
}[batchid]
# Set up data
N = 200
x = np.linspace(0, 4*np.pi, N)
y = func(x)
source = ColumnDataSource(data=dict(x=x, y=y))
# Set up plot
plot = figure(plot_height=400, plot_width=400, title="my wave",
tools="crosshair,pan,reset,save,wheel_zoom",
x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
# Set up widgets
text = TextInput(title="title", value="Batch n°{}".format(batchid))
offset = Slider(title="offset", value=0.0, start=-5.0, end=5.0, step=0.1)
amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0)
phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1)
# Set up callbacks
def update_title(attrname, old, new):
plot.title.text = text.value
text.on_change('value', update_title)
def update_data(attrname, old, new):
# Get the current slider values
a = amplitude.value
b = offset.value
w = phase.value
k = freq.value
# Generate the new curve
x = np.linspace(0, 4*np.pi, N)
y = a*func(k*x + w) + b
source.data = dict(x=x, y=y)
for w in [offset, amplitude, phase, freq]:
w.on_change('value', update_data)
# Set up layouts and add to document
inputs = column(text, offset, amplitude, phase, freq)
curdoc().add_root(row(inputs, plot, width=800))
Started with
bokeh serve bokeh_server.py --allow-websocket-origin=127.0.0.1:5000
flask_server.py
'''This example demonstrates embedding an autoloaded Bokeh server
into a simple Flask application, and passing arguments to Bokeh.
To view the example, run:
python flask_server.py
in this directory, and navigate to:
http://localhost:5000
'''
import atexit
import subprocess
from flask import Flask, render_template_string
from bokeh.embed import server_document
home_html = """
<!DOCTYPE html>
<html lang="en">
<body>
<div class="bk-root">
<h1><a href="/batch/1"> Batch 1 (cos)</a></h1>
<h1><a href="/batch/2"> Batch 2 (sin)</a></h1>
<h1><a href="/batch/3"> Batch 3 (tan)</a></h1>
</div>
</body>
</html>
"""
app_html = """
<!DOCTYPE html>
<html lang="en">
<body>
<div>
<h2><a href="/batch/1">Batch 1 (cos)</a> - <a href="/batch/2">Batch 2 (sin)</a> - <a href="/batch/3">Batch 3 (tan)</a></h2>
</div>
{{ bokeh_script|safe }}
</body>
</html>
"""
err_html = """
<!DOCTYPE html>
<html lang="en">
<body>
<div>
<h2>{{ error }}</h2>
</div>
</body>
</html>
"""
app = Flask(__name__)
bokeh_process = subprocess.Popen(
['python', '-m', 'bokeh', 'serve', '--allow-websocket-origin=localhost:5000', 'bokeh_server.py'], stdout=subprocess.PIPE)
@atexit.register
def kill_server():
bokeh_process.kill()
@app.route('/')
def home():
return render_template_string(home_html)
@app.route('/batch/<int:batchid>')
def visualization(batchid):
bokeh_script = server_document(url='http://localhost:5006/bokeh_server', arguments=dict(batchid=batchid))
return render_template_string(app_html, bokeh_script=bokeh_script)
@app.errorhandler(404)
def page_not_found(e):
return render_template_string(err_html, error = "404 Not found")
@app.errorhandler(403)
def page_not_found(e):
return render_template_string(err_html, error = "403 Forbidden")
@app.teardown_request
def teardown_request_func(error=None):
print("teardown_request is running")
if error:
# log error
print(str(error))
if __name__ == '__main__':
app.run(debug=True)
Flask server started with python flask_server.py
When running this and going to localhost:5000
, when one chooses a link I get to the app_html
page but no embedded plot is shown due to 403 page error reported by Bokeh server. I would like to capture this error and show to the user that there is a page error - how do I do that?
Firefox console:
Request URL:http://localhost:5006/bokeh_server/ws?bokeh-protocol-version=1.0&bokeh-session-id=MgpZNLFpFlZDDacVqZzjLlWZHmcjCngpWn19QiIZnyXs
Request method:GET
Remote address:127.0.0.1:5006
Status code:
403
Version:HTTP/1.1
Have tested in Bokeh version 1.3.4 (py2.7) and 2.1.1 (py3.6.8), both on Redhat 7.