Hosting a Flask + Bokeh Server app in Heroku

Hi there,
I’ve been exploring with Bokeh for the last several days. I have tried a lot of your examples and also been able to create some plots myself. I also had success in Deploying Bokeh Apps at Heroku. Bokeh really rocks!
Now I’m trying to embed Bokeh app using the Flask frame work.

  • Was able to embed static Bokeh (script and div) into Flask framework and deploy it to Heroku
  • Was able to embed Bokeh Server app into Flask locally in my machine, but I’m stuck with deploying in Heroku.

I’m able to see at my browser the tag generated by the server_document function, but right now Chrome is fighting with http and https mixed content. I don’t know if the Bokeh server is running at Heroku (any way I can configure logging to be re-routed from the Bokeh server to the main server?)

Does anyone has a minimal working example in Github?

Thank you in advance.

This code snippet is from the bokeh-master/examples/howto/embed/server_embed/flask_gunicorn_embed.py

# 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("localhost", 0)

@app.route('/', methods=['GET'])
def bkapp_page():
    script = server_document('http://localhost:%d/bkapp' % 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"])
    bokeh_http = HTTPServer(bokeh_tornado)
    bokeh_http.add_sockets(sockets)
    server = BaseServer(IOLoop.current(), bokeh_tornado, bokeh_http)
    server.start()
    server.io_loop.start()

Hi @hmanuel1 please edit your post to use code formatting so that the code is intelligible (either with the </> icon on the editing toolbar, or triple backtick ``` fences around the code blocks)

I think I know what is going on…Please correct my understanding…

  1. I’m using one Dyno (aka a container) in Heroku to access the Flask App (e.g. port: 8000 in the example.) I can’t assign an specific port number. Heroku will assign one for you.

  2. My app will create worker threads with sockets binded to available ports. This would be port 5000 in the example.

  3. Heroku will forward HTTPS requests hitting Heroku at port 8000 to Flask, Flask will respond with the Flask page with the Bokeh tag generated by the server_document function embedded.

  4. Now, the user’s browser will try to autoload Bokeh server content based on the script embedded in the Flask page at step 3 using XMLHttpRequest.

  5. However, there would be two issues at this point:

    1. Browser will reject a page with mixed http and https content.
    2. If you get around 1, but doing an http request, the Bokeh server at port: 5000 will be unreachable since the only entry point is at port: 8000.

If my understanding is correct, I can think in several ways to fix it. For example, creating two apps at Heroku, each will have a unique entry point. One with Flask and the other with the Bokeh Server as the main thread. This will work like in the example running in local machine.

Alternatively, I would setup Flask (public facing) as a proxy to the Bokeh Server. I just don’t know if anyone has tried this approach and how bokeh performance would be impacted.

Has anyone had success in deploying the Flask + Bokeh Server in Heroku within one Heroku App with only one entry point port.

Thanks!!!

Bokeh uses Tornado. I would probably just wrap the Flask app in a Tornado proxy that uses the same port but different paths for the Flask part and for the Bokeh part.

Thank you for the quick response.

I have a prototype almost working using a reverse-proxy implemented in Flask, there is not Bokeh URLs hitting my browser, everything is loading with no CORS errors or mixed protocol errors. There is one last piece that I need to solve:
I have Bokeh Server running at http://localhost:5000/

This is the GET request to autoload the Bokeh Server content into Flask:
/bkapp/autoload.js?bokeh-autoload-element=1000&bokeh-app-path=/bkapp&bokeh-absolute-url=http://localhost:5000/bkapp&resources=none

Could you explain the bokeh-absolute-url vs bokeh-relative-url in the context or a reverse-proxy setup. I tried to set this to relative-url, but the Bokeh server is complaining of a missing handshake even though I don’t require to load any resources?

Also, I have set the resources to None in server_document, so I can template the CDN resources separately. If I do this, is there any other resource that I need to worry about?

Thanks!

Sorry, I don’t know the specifics. In my own applications, I always load Bokeh and its documents manually.

After research, I think the best way to have Flask + Bokeh Server running in Heroku within a single Heroku App is by setting up a reverse-proxy server such as NGINX and running the Bokeh Server behind NGINX.

For anyone interested in my first post, I did try to run a reverse-proxy with Flask only. I was able to do the HTTP part, but it got more and more complex when I started handling the WebSocket connection between the Bokeh Server and the Client Browser. Here is what I learned:


sequenceDiagram

    participant A as Client Browser
    participant B as Bokeh Server <bkapp>
    A->>B: HTTP Request bkapp/autoload.js <params>
    B->>A: HTTP Response bkapp/autoload.js script
    A->>B: HTTP Request bkapp/ws
    B->>A: HTTP Response Can "Upgrade" only to "WebSocket"
    A->>B: WebSocket connect
    B->>A: WebSocket connection handshake
    B->>A: WebSocket <json> messages from Bokeh to Browser
    A->>B: WebSocket <json> messages from Browser to Bokeh

The HTTP part can be handled by Flask with very little overhead, but the WebSocket part, I tried Tornado WSGIContainer, but Tornado’s own documentation explains that it will not scale as Flask would. I also tried Flask-SocketIO and Flask-SocketIO-Client. At this point, the solution explained in Bokeh documentation with Apache, NGINX or establishing an SSL tunnel look way more simpler.

@Bryan, @p-himik,
I was able to host the Flask + Bokeh Server app within one Herokup app. No NGINX or Apache or any other external appliances needed.

Approach Used:

  1. I configured the jinja2 template to inject CDN resource scripts instead of the Bokeh static/js that is set up by default, using a custom function to create those scripts based on the local installed Bokeh version. Perhaps this can be optimized a bit by using Bokeh Resources class.

  2. From @p-himik hint, I wrapped the Flask application to run under Tornado.

  3. I used the Flask framework to create an HTTP reverse-proxy server (only supports GET method at this time.) However, this worked until I started receiving Web Socket requests.

  4. Finally, I created a Web Socket reverse-proxy for each connection opened by a client (browser) using Tornado web sockets. This part was the most challenging as I had to create/use multiple tools and code using the async programming approach from Tornado.

Results:

  1. I was expecting a bit of latency, but for my pleasant surprise redirecting the static/js resources to use CDN improved responsiveness quite a bit.
  2. This solution doesn’t require a second app in Heroku or build a complex reverse-proxy using Apache or NGINX.
  3. Where security is no a concern, it provides a frame work for development and test.
  4. Eases quite a bit integration concerns with the major frameworks with almost everything done on the user side in Python.

Code:

  1. I have this app currently running at https://safe-scrubland-67589.herokuapp.com/
  2. Code is at https://github.com/hmanuel1/covid/tree/master/app/utest

I’m sure that I’m missing a couple of components. For example, I see in the Bokeh code support for POST, but I would like to understand how is used before I add support for it. I would like to contribute to the project, could you send me information to participate.

I would like to post the code in here, but it’s a project with several files. I see an “upload” option in this post. Please let me know where is the best way.

Thanks and keep the great work!
hmanuel1

1 Like