Getting 404 error for embedded Bokeh app

My issue is similar to the following two issues that I found, but not quite the same, in that I’m not serving a standalone app or embedding in Django. Rather, I’m trying to serve a Tornado app with an embedded inline Bokeh server. (The bokeh code and some of the other code need to share resources

bokeh-serve-error-404-for-js-ressources/5708
serving-bokeh-apps-embedded-in-django-from-subpath/8359

I was able to build and test the app on my computer, but I am now trying to host it as a web app, and it looks like it’s pulling the static resources from the wrong place. It’s probably a configuration issue. Here is a minimal example using one of the stock Bokeh examples in the gallery. It works just fine when I run locally in the command line and browse to localhost:8888 but not when I try to host it. (tearlant.com is my domain so this is what I see when I try to host there)

My main.py

import os
import nest_asyncio
import tornado.httpserver
import tornado.ioloop
import tornado.websocket
import tornado.web
import tornado.options
import numpy as np

from bokeh.client import pull_session
from bokeh.embed import server_session
from bokeh.plotting import figure
from bokeh.server.server import Server


def bokeh_app(doc):
    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 = np.array([[r, g, 150] for r, g in zip(50 + 2 * x, 30 + 2 * y)], dtype="uint8")

    TOOLS = "hover,crosshair,pan,wheel_zoom,zoom_in,zoom_out,box_zoom,undo,redo,reset,tap,save,box_select,poly_select,lasso_select,examine,help"

    p = figure(tools=TOOLS)

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

    doc.add_root(p)


# Tornado handlers
class BaseHandler(tornado.web.RequestHandler):
    def data_received(self, chunk):
        pass

    def get(self):
        ioloop = tornado.ioloop.IOLoop.current()

        with pull_session(url="https://www.tearlant.com/ts-bokeh/bokeh_app", io_loop=ioloop) as mysession:
            script = server_session(session_id=mysession.id, url="https://www.tearlant.com/ts-bokeh/bokeh_app")
            self.render("bokeh_app.html", scr=script, username='', api_call_successful=False, api_call_data=None, distance=None, airport_code=None)


class TornadoApplication(tornado.web.Application):
    def __init__(self):
        handlers = [
            (r"/", BaseHandler)
        ]
        settings = dict(
                template_path=os.path.join(os.path.dirname(__file__), "templates"),
                static_path=os.path.join(os.path.dirname(__file__), "static"),
                #xsrf_cookies=True,
                #cookie_secret="YOUR SECRET HERE",
                debug=True
        )
        super().__init__(handlers, **settings)


if __name__ == '__main__':
    nest_asyncio.apply()

    tornado.options.define("port", default=8888, help="run on the given port", type=int)

    http_server = tornado.httpserver.HTTPServer(TornadoApplication())
    http_server.listen(tornado.options.options.port)
    io_loop = tornado.ioloop.IOLoop.current()

    bokeh_server = Server({'/bokeh_app': bokeh_app},
                          io_loop=io_loop,
                          allow_websocket_origin=['localhost:8888'],
                          check_unused_sessions_milliseconds=1000,
                          unused_lifetime_milliseconds=1000
                          )

    print("App initialized.")

    io_loop.start()

The template file (I just changed the background colour so that I can confirm it’s being served to the browser, even if the bokeh app is not properly embedded)
bokeh_app.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Prototype</title>
    <style>
        body {background-color: powderblue;}
    </style>
    </head>
<body>
<div class="columns">
    <div class="column">
        {% raw scr %}
    </div>
</div>

</body>
</html>

And the following is added to my nginx configuration file.

	location /troubleshooting-bokeh/ {
			rewrite ^/troubleshooting-bokeh(/.*)$ $1 break;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header Host $host;
			proxy_pass http://localhost:8888;

			proxy_http_version 1.1;
			proxy_set_header Upgrade $http_upgrade;

			proxy_set_header Connection "upgrade";
	}
	location /ts-bokeh/ {
			rewrite ^/ts-bokeh(/.*)$ $1 break;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header Host $host;
			proxy_pass http://localhost:5006;

			proxy_http_version 1.1;
			proxy_set_header Upgrade $http_upgrade;
			proxy_set_header Connection "upgrade";
	}

I would expect the app to be served at www.tearlant.com/troubleshooting-bokeh, and I can tell the nginx configuration is correct, because I changed the background colour of the HTML file. I’m essentially seeing an empty blue page with a blue background, with the above messages in the console.

This is probably a configuration issue. When running locally, if I replace the URL root in pull_session and server_session with localhost:5006, it renders just fine.

The 404s tell the story. By default Bokeh apps look for BokehJS resources at /static paths on the same server as the app. But you have not included anything about /static in your nginx config. You can either:

  • update nginx to handle /static routes, or
  • set BOKEH_RESOURCES=cdn environment var, then Bokeh will load published BokehJS resources from external cdn.bokeh.org instead of yoru server

That does make sense, but I’m not sure where to direct it. Where would the static resources be located? Logically if it runs locally, them static folder should be at http://localhost:8888/static but it does not work.

I’ve tried

        location /static/ {
                rewrite ^/static(/.*)$ $1 break;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $host;
                proxy_pass http://localhost:8888/static;

                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
        }

but that produces several 404 errors in the terminal, which means it’s still looking in the wrong place:

Since the static seems to have been dropped from the location, I also tried

        location /static/ {
                rewrite ^/static(/.*)$ /static$1 break;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $host;
                proxy_pass http://localhost:8888;

                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
        }

But this produces the same 404s (only with static prepended to the locations)

I got it working. For anybody in the future who has a similar issue, here’s my solution.

  1. Find the location of the bokeh package, whether global or in a virtual environment, by opening python and typing
import bokeh
bokeh.__file__
  1. Copy the static folder to a location where it can be served, for exmple
sudo cp -R path/to/bokeh/server/static /var/www/bokeh/static/
  1. Give the proper permissions to the folders and files, in this example, from /var/www/,
sudo chown -R www-data:www-data bokeh
sudo find bokeh/ -type d -exec chmod 755 {} \;
sudo find bokeh/ -type f -exec chmod 644 {} \;
  1. In the nginx .conf file,
        location /static/ {
                root /var/www/bokeh/;
        }
  1. Allow websocket origins from the client using the hostname. Since my domain name is www.tearlant.com, the line in my main.py file is
    bokeh_server = Server({'/bokeh_app': bokeh_app},
                          io_loop=io_loop,
                          allow_websocket_origin=['localhost:8888', 'www.tearlant.com'],
                          check_unused_sessions_milliseconds=1000,
                          unused_lifetime_milliseconds=1000
                          )

And now it serves the content properly.

That’s definitely a possibility, and something similar is describe in the docs, because it has the benefit of letting Nginx, which is highly optimized for serving static dirs, to serve the static dirs and take the load of the Bokeh server. But using CDN resources has the same benefit and at least IMO, is less work.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.