Running on a web server

I am trying to run a bokeh app with flask on my web server. I am using the example at (exactly the same code)

Upon running everything fires up just fine. When I look at the problems in the source code I can see:

serve.py communicates with the bokeh_app.py fine. I can see that it has connected. But unfortunately nothing displays, except for the text already in the html script. Has anyone else come across this?

Are you running the Bokeh server with the command line arguments in the README? You have to grant permission for the embedding page to actually do the embedding by allow-listing the origin (as done there)

Hi Bryan. Yes I ran with bokeh serve. And allowed the Web sockets indicated. The only difference is that it’s running through a python Web app so with a passenger_wsgi.py file calling the serve.

Just to be clear, the value there is only illustrative of that one example. You need to set the allowed websocket origin to match the actual HTTP origin header for your page.

bokeh serve --port 5100 --allow-websocket-origin localhost:8080 --allow-websocket-origin 127.0.0.1:8080 --allow-websocket-origin mywebsite.com:8080 bokeh_app.py

Is the command that I ran. Where mywebsite.com is the name of my webpage

is your site actually running to the public on port 8080?

BTW the Bokeh server log itself will conclusively state whether this is the issue, and, if so, also tell you exactly what origin would need to be specified.

> (app4:3.7)[[email protected] app4]$ bokeh serve --port 5100 --allow-websocket-origin localhost:8080 --allow-websocket-origin 127.0.0.1:8080 bokeh_app.py --allow-websocket-origin nswpropertyprices.com.au:8080 --log-level debug
> 2021-09-15 13:58:12,044 Starting Bokeh server version 2.3.3 (running on Tornado 6.1)
> 2021-09-15 13:58:12,045 User authentication hooks NOT provided (default user enabled)
> 2021-09-15 13:58:12,045 These host origins can connect to the websocket: ['localhost:8080', 'nswpropertyprices.com.au:8080', '127.0.0.1:8080']
> 2021-09-15 13:58:12,045 Patterns are:
> 2021-09-15 13:58:12,046   [('/bokeh_app/?',
> 2021-09-15 13:58:12,046     <class 'bokeh.server.views.doc_handler.DocHandler'>,
> 2021-09-15 13:58:12,046     {'application_context': <bokeh.server.contexts.ApplicationContext object at 0x2accdcfe6950>,
> 2021-09-15 13:58:12,046      'bokeh_websocket_path': '/bokeh_app/ws'}),
> 2021-09-15 13:58:12,046    ('/bokeh_app/ws',
> 2021-09-15 13:58:12,046     <class 'bokeh.server.views.ws.WSHandler'>,
> 2021-09-15 13:58:12,046     {'application_context': <bokeh.server.contexts.ApplicationContext object at 0x2accdcfe6950>,
> 2021-09-15 13:58:12,046      'bokeh_websocket_path': '/bokeh_app/ws',
> 2021-09-15 13:58:12,046      'compression_level': None,
> 2021-09-15 13:58:12,046      'mem_level': None}),
> 2021-09-15 13:58:12,046    ('/bokeh_app/metadata',
> 2021-09-15 13:58:12,046     <class 'bokeh.server.views.metadata_handler.MetadataHandler'>,
> 2021-09-15 13:58:12,046     {'application_context': <bokeh.server.contexts.ApplicationContext object at 0x2accdcfe6950>,
> 2021-09-15 13:58:12,046      'bokeh_websocket_path': '/bokeh_app/ws'}),
> 2021-09-15 13:58:12,046    ('/bokeh_app/autoload.js',
> 2021-09-15 13:58:12,046     <class 'bokeh.server.views.autoload_js_handler.AutoloadJsHandler'>,
> 2021-09-15 13:58:12,046     {'application_context': <bokeh.server.contexts.ApplicationContext object at 0x2accdcfe6950>,
> 2021-09-15 13:58:12,046      'bokeh_websocket_path': '/bokeh_app/ws'}),
> 2021-09-15 13:58:12,047    ('/?',
> 2021-09-15 13:58:12,047     <class 'bokeh.server.views.root_handler.RootHandler'>,
> 2021-09-15 13:58:12,047     {'applications': {'/bokeh_app': <bokeh.server.contexts.ApplicationContext object at 0x2accdcfe6950>},
> 2021-09-15 13:58:12,047      'index': None,
> 2021-09-15 13:58:12,047      'prefix': '',
> 2021-09-15 13:58:12,047      'use_redirect': True}),
> 2021-09-15 13:58:12,047    ('/static/extensions/(.*)',
> 2021-09-15 13:58:12,047     <class 'bokeh.server.views.multi_root_static_handler.MultiRootStaticHandler'>,
> 2021-09-15 13:58:12,047     {'root': {}}),
> 2021-09-15 13:58:12,047    ('/static/(.*)',
> 2021-09-15 13:58:12,047     <class 'bokeh.server.views.static_handler.StaticHandler'>)]
> 2021-09-15 13:58:12,049 Bokeh app running at: http://localhost:5100/bokeh_app
> 2021-09-15 13:58:12,049 Starting Bokeh server with process id: 18005
> 2021-09-15 13:58:27,064 [pid 18005] 0 clients connected
> 2021-09-15 13:58:27,065 [pid 18005]   /bokeh_app has 0 sessions with 0 unused
> 2021-09-15 13:58:35,912 Subprotocol header received
> 2021-09-15 13:58:35,912 WebSocket connection opened
> 2021-09-15 13:58:35,982 Receiver created for Protocol()
> 2021-09-15 13:58:35,982 ProtocolHandler created for Protocol()
> 2021-09-15 13:58:35,982 ServerConnection created
> 2021-09-15 13:58:36,024 Sending pull-doc-reply from session 'GhjfA7XvEcZx14O3CVohJJTQ1CnMDmZrp4vq7StBWd0W'
> 2021-09-15 13:58:36,165 WebSocket connection closed: code=1000, reason='closed'
> 2021-09-15 13:58:42,051 [pid 18005] 0 clients connected
> 2021-09-15 13:58:42,051 [pid 18005]   /bokeh_app has 1 sessions with 1 unused

When running the command mentioned before this is the log. I run app.serve(port=8080) So it seems that serve.py is connecting, but then when it gets to embed the snippet it in the html it doesnt allow it? It is the above that you are referring to when you mentioned the Bokeh server log?

As for the port, I have also tried it with port 80 so i can just go to the root url aswell but no luck

That indicates a websocket connection was made and a session was created (so the allowed ws origin is correct) but then ~immediately closes. You might look in the browser dev tools to see if there is any indication why the websocket was closed, or try a different browser to see if that affects things.

Hi Bryan, no luck when switching browsers. But on top of that I moved from a shared web server running python apps to my own VPS running on top of apache as I thought that would help the problem but we still get exactly the same error. The websocket definitely connects, as the script is generated. But then when it tries to display (autoload js) it in the web page its as if the socket dies before that happens.

Are you sending a large amount of data? Perhaps the max websocket size needs increasing (by default Bokeh does not change the underlying Tornado default). Is there any sort of proxy in between?

I’m only trying to replicate that minimal example from the Bokeh github repo. So not much data at all. I have tried with the max-message-size set to a large value but still the same issue. I will investigate the proxy situation. Thanks

Just to be clear, does the example in the repo work for you as-is? It definitely functions as expected for me:

Yes Correct, it works on localhost just fine. Now I am trying to embed it onto my website (with flask and apache). And that’s when I’ve been having the issues.

Runing’ bokeh serve’ everything seems to be fine. Then I navigate to the web page the websocket connection opens, script is generated with server_session() then the websocket closes pretty instantly. And then we get the Failed to load resource: net::ERR_CONNECTION_REFUSED.

The rest of the html generates just fine so its just the autoload.js part of the script that is not connecting to the bokeh server (and potentially due to the fact that its closing pretty quickly). So just to reiterate my setup:

init.py

import bokeh
from bokeh.client import pull_session
from bokeh.embed import server_session,server_document
import time
from datetime import datetime


app = Flask(__name__)


app_url = "http://127.0.0.1:5100/bokeh_app"

@app.route('/',methods=['GET'])
def bkapp_page():
    # pull a new session from aunning Bokeh server
    with pull_session(url=app_url) as session:
        # time.sleep(10)
        # update or customize that session

        #session.document.roots[0].title.text = "Special Plot Title For A Specific User!"



        # generate a script to load the customized session

        script = server_session(session_id=session.id, url=app_url)
        #script = server_document(url=app_url,relative_urls=True)
        print(datetime.now())
        print(script)
        # use the script in the rendered page

        return render_template("embed.html", script=script, template="Flask")

if __name__ == "__main__":
   app.run(debug=True)

bokeh_app.py

from bokeh.io import curdoc
from bokeh.plotting import figure

N = 4000
x = np.random.random(size=N) * 100
y = np.random.random(size=N) * 100
radii = np.random.random(size=N) * 1.5
colors = [
    "#%02x%02x%02x" % (int(r), int(g), 150) for r, g in zip(50+2*x, 30+2*y)
]

p = figure(tools="", toolbar_location=None)

p.circle(x, y, radius=radii,
         fill_color=colors, fill_alpha=0.6,
         line_color=None)

curdoc().add_root(p)

templates/embed.html

<!doctype html>



<html lang="en">

<head>

  <meta charset="utf-8">

  <title>Embedding a Bokeh Server With {{ framework }}</title>

</head>

<body>

  <div>

    This Bokeh app below served by a Bokeh server that has been embedded

    in another web app framework. For more information see the section

    <a  target="_blank" href="https://docs.bokeh.org/en/latest/docs/user_guide/embed.html#bokeh-applications">Embedding Bokeh Applications</a>

    in the User's Guide.

  </div>

  {{ script | safe }}

</body>

</html>
/etc/apache2/sites_available/FlaskApp.conf
<VirtualHost *:80>
  ServerName <www.mysite.com>
  ServerAlias <mysite.com>
  ServerAdmin <[email protected]>
  WSGIScriptAlias / /var/www/FlaskApp/flaskapp.wsgi

  ProxyPreserveHost On
  ProxyPass /bokeh_app/ws ws://<myipaddress>/bokeh_app/ws
  ProxyPassReverse /bokeh_app/ws ws://<myipaddress>/bokeh_app/ws

  ProxyPass /bokeh_app http://<myipaddress>/bokeh_app/
  ProxyPassReverse /bokeh_app http://<myipaddress>/bokeh_app/

    <Directory />
        Require all granted
        Options -Indexes
    </Directory>


  <Directory /var/www/FlaskApp/FlaskApp/>
    Order allow,deny
    Allow from all
  </Directory>

  Alias /static /var/www/FlaskApp/FlaskApp/static
   <Directory /var/www/FlaskApp/FlaskApp/static/>
    Order allow,deny
    Allow from all
    Options +Indexes
  </Directory>

  ErrorLog ${APACHE_LOG_DIR}/error.log
  LogLevel warn
  CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

And when I run things. I call

bokeh serve --port 5100 --allow-websocket-origin=myipaddress bokeh_app.py --log-level debug

Now when i visit my ip address (I don’t have dns forwarding yet so just working with the IP address until i get this figured)

So obviously in the above i have replaced my website and IP address with aliases. Perhaps something to do with setting up a reverse proxy?

So thinking about it more, it did not occur to me that there will be a WS connection from the pull_session in the Flask app, and I suspect that is what is reflected in the Bokeh server logs, especially since 1000 indicates a normal, expected closure. So we are are back to it being the browser that is having a refused connection, which is typically a bad origin allow-list. What happens if just specify the "*" wildcard for the origin? That should disable all origin checking and allow WS connections from any origin. You can also increase both the Python and JS log levels (see the env vars in bokeh.settings).

yep I have tried the wildcard. Same outcome. I set export BOKEH_DEV=true to see if it produced anymore interesting logs. But everything looks exactly the same so far (unless these logs are saved somewhere else)

Given that, and given that there is not even a second attempt to connect in the Bokeh server logs, that would actually indicate that the connection attempt from the browser is never even making it that far, i.e it must be getting blocked earlier by the Apache proxy. Perhaps there are clues in the Apache logs, but Apache configuration is outside my expertise so I can’t really suggest more than looking at/for them.

No worries Bryan. The Apache logs don’t seem to indicate an error. But appreciate the help. Will have to keep investigating. Maybe someone else might see this and have a good idea aswell. Appreciate it