Reverse-proxying: Flask app with Bokeh server on Nginx

J K cas.xyz at googlemail.com
Wed May 17 10:34:17 UTC 2017


Hi Francis,

Initially, without https, everything was running well. I was deploying a
Bokeh server with the 'company_abc' app, and used a Flask app to render the
website and handle login/redirecting/etc. Everything was behind a Nginx
server.

Then, I installed https the Bokeh app would not be rendered on the website.
The Flask app worked fine, as before.

I think what's happening is that Nginx sends a request to the Flask app.
Flask then pulls the Bokeh session with

session=pull_session
<http://bokeh.pydata.org/en/0.12.4/docs/reference/client.html#bokeh.client.session.pull_session>
(url=url,app_path="/company_abc")

and creates a script tag with

bokeh_script=autoload_server
<http://bokeh.pydata.org/en/latest/docs/reference/embed.html#bokeh.embed.autoload_server>
(None,app_path="/company_abc/",session_id=session.id,url=url_https)

That's then embedded into the html page by

return render_template("company_abc.html", bokeh_script=bokeh_script)

The script tag looks like this:

<script

src="https://example.com/company_abc/autoload.js?bokeh-
autoload-element=c5c9bdb5-40e8-46a2-9bf0-40a9d396ce97"
id="c5c9bdb5-40e8-46a2-9bf0-40a9d396ce97"

data-bokeh-model-id=""

data-bokeh-doc-id=""

></script>

Now, when the browser opens the 'company_abc.html' page it sends a request
to the Nginx server. This should then proxy to the Bokeh server.
Does this sound correct?

Now, I have done some changes and get a different error. The Bokeh app I'm
starting now with

bokeh serve company_abc.py --allow-websocket-origin=example.com
 --allow-websocket-origin=www.example.com --port=5006 \
--host="*" --use-xheaders


So, it's running on localhost, port 5006. In the Flask app I redefined the
route to the app as follows:

@app.route("/company_abc/")
def company_abc():
    url='http://127.0.0.1:5006/'
    session=pull_session(url=url,app_path="/company_abc")
    url_https='https://example.com/'
    bokeh_script=autoload_server(None,app_path="/company_abc/",session_id=
session.id,url=url_https)
    return render_template("company_abc.html", bokeh_script=bokeh_script)


For the Nginx config file I followed the template from the Bokeh User Guide
<http://bokeh.pydata.org/en/latest/docs/user_guide/server.html#reverse-proxying-with-nginx-and-ssl>
:

location /company_abc/ {
                  proxy_pass http://127.0.0.1:5006;
                  proxy_set_header Upgrade $http_upgrade;
                  proxy_set_header Connection "upgrade";
                  proxy_http_version 1.1;
                  proxy_set_header X-Forwarded-Proto $scheme;
                  proxy_set_header X-Forwarded-For
$proxy_add_x_forwarded_for;
                  proxy_set_header Host $host:$server_port;
                  proxy_buffering off;
        }
}


Now, with these setting I get the following errors in Chrome:

GET https://example.com/static/css/bokeh.min.css?v=
7246afcfffc127faef7c138bce4742e9
example.com/:9
GET https://example.com/static/css/bokeh-widgets.min.css?v=
d9cb9322d940f107727b091ff98d9c70
example.com/:12
GET https://example.com/static/js/bokeh-widgets.min.js?v=
1af1302b8bd7fcc88c7bcafb8771497b
example.com/:11
GET https://example.com/static/js/bokeh.min.js?v=
9d3af13f493d36073a89714f6a5240c6
example.com/:12
GET https://example.com/static/js/bokeh-widgets.min.js?v=
1af1302b8bd7fcc88c7bcafb8771497b 404 (NOT FOUND)
(index):14 Uncaught ReferenceError: Bokeh is not defined
    at (index):14
(anonymous) @ (index):14
(index):37 Uncaught ReferenceError: Bokeh is not defined
    at HTMLDocument.fn ((index):37)


The log file for the Bokeh server shows no errors:

2017-05-17 10:17:49,640 Starting Bokeh server version 0.12.4
2017-05-17 10:17:49,641 Host wildcard '*' can expose the application to
HTTP host header attacks. Host wildcard should only be used for testing
purpose.
2017-05-17 10:17:49,647 Starting Bokeh server on port 5006 with
applications at paths ['/geomorphix']
2017-05-17 10:17:49,647 Starting Bokeh server with process id: 15851
2017-05-17 10:18:08,829 200 GET /geomorphix/ (37.201.192.96) 845.69ms

Interestingly, when I manually execute the lines

url='http://127.0.0.1:5006/'
session=pull_session(url=url,app_path="/company_abc")
url_https='https://example.com/'
bokeh_script=autoload_server(None,app_path="/company_abc/",session_id=
session.id,url=url_https)


The Bokeh log file says that a WebSocket is opened and a ServerConnection
is created:

2017-05-17 10:21:09,915 WebSocket connection opened
2017-05-17 10:21:10,769 ServerConnection created


What does the new error mean?
Why is a WebSocket opened and ServerConnection created only when I manually
pull a session?

Thanks!

Message: 3
> Date: Tue, 16 May 2017 17:49:07 +0100
> From: Francis Daly <francis at daoine.org>
> To: J K via nginx <nginx at nginx.org>
> Cc: J K <cas.xyz at googlemail.com>
> Subject: Re: Re :Re: Re:Reverse-proxying: Flask app with Bokeh server
>         on Nginx
> Message-ID: <20170516164907.GE10157 at daoine.org>
> Content-Type: text/plain; charset=utf-8
>
> On Mon, May 15, 2017 at 11:59:27AM +0200, J K via nginx wrote:
>
> Hi there,
>
> To recap:
>
> you had installed a "flask" web server and a "bokeh" web server. You
> put the "flask" one behind nginx, so that clients would talk to nginx
> and to bokeh.
>
> And the clients were happy to talk http to nginx and http to bokeh.
>
> Then you enabled https on nginx, so that clients would talk https to
> nginx and http to bokeh.
>
> And the clients did not want to talk http to bokeh after talking https
> to nginx.
>
> So right now, you are trying to put the "bokeh" web server behind
> nginx too.
>
> There are various ips and ports and url prefixes that appear in various
> configuration files; it is worth making sure that you are very clear on
> what each one is for. That will make it possible to see what needs to
> be done to put bokeh behind nginx.
>
>
> > > >  3. in the Flask app, I changed the URL
> > > > to:url='https://138.197.132.46:5006/bokeh/'
>
> > > You can't mix 'https://' and :5006 port  in same url - this way the
> > > request
> > > goes to port 5006 but it expects to be also encrypted but if I
> understand
> > > correctly bokeh doesn't support SSL.
>
> > > What I forgot to add you need to change the 'url' (note the domain
> part)
> > > to:
> > >
> > > url='https://yourdomain/bokeh/'
> > >
> > > by looking at your error messages it seems that the 'url' is also
> directly
> > > used for client requests (probably placed in the html templated) -
> which
> > > means you can't use plain IP because then the browser most likely will
> just
> > > generate a SSL certificate and domain mismatch.
>
> > Thanks for answering again.
> >
> > I followed your advise and change the Flask app script so that I have one
> > URL to pull the Bokeh session and another one to create the HTML script:
> >
> > def company_abc():
> >
> > url='http://127.0.0.1:5006/bokeh'
>
> So: what is that url for?
>
> Is it a thing that the client web browser will try to access, or a thing
> that something internal to flask will try to access, or a thing that
> something internal to bokeh will try to access?
>
> When you can say what it is, then it may become clear what value it
> should have.
>
> > session=pull_session(url=url,app_path="/company_abc")
> >
> > url_https='https://www.example.com'
>
> Same question. What is the purpose of that? Which of (browser, flask,
> bokeh) will try to use it?
>
> > > >                    proxy_pass http://127.0.0.1:5006;  # you
> suggested
> > > 127.0.
> > > > *1*.1, but I figured that was a typo
> > >
> > > The proxy_pass address should be wherever your "bokeh" http server is
> > > actually listening.
> > >
> > > Which probably means that whatever you use up there...
> > >
> > > > command=/opt/envs/virtual/bin/bokeh serve company_abc.py
> company_xyz.py
> > > > geomorphix.py --prefix=/bokeh/ --allow-websocket-origin=www.
> example.com
> > > > --allow-websocket-origin=example.com --host=138.197.132.46:5006
> > > > --use-xheaders
> > >
> > > you should also use up there as --host.
> > >
> > > I suspect that making them both be 127.0.0.1 will be the easiest
> > > way of reverse-proxying things; but I also suspect that the
> > > "--allow-websocket-origin" part suggests that you may want to configure
> > > nginx to reverse proxy the web socket connection too. Notes are at
> > > http://nginx.org/en/docs/http/websocket.html
> > >
> > > It will be helpful to have a very clear picture of what talks to what,
> > > when things are working normally; that should make it easier to be
> > > confident that the same links are in place with nginx in the mix.
>
> > As you suggested, I did the following:
> >
> > 1. in '/etc/supervisor/conf.d/bokeh_serve.conf' I changed the host to
> > 127.0.0.1:
> >
> > [program:bokeh_serve]
> >
> > command=/opt/envs/virtual/bin/bokeh serve company_abc.py
> --prefix=/bokeh/
> > --allow-websocket-origin=www.example.com --allow-websocket-origin=
> > example.com --host=127.0.0.1:5006 <http://138.197.132.46:5006/>
> >  --use-xheaders
>
> What is "--allow-websocket-origin" for? Is it causing any breakage here?
>
> (Can you temporarily run with all websocket origins allowed, until
> things work; and then add back the restrictions to confirm that things
> still work?)
>
> > 2. I configure nginx to reverse proxy the web socket connection by adding
> > the following lines to each location block in
> '/etc/nginx/sites-available/
> > default':
>
> That may or may not be needed in "each location". Maybe it is only needed
> in the "bokeh" location; the intended data flow diagram will show how
> things should be configured.
>
> > 3. In the Flask web app code I changed the URL of the route accordingly
> to
> > 127.0.0.1:
> >
> > @app.route("/company_abc/")
> >
> > @login_required
> >
> > @roles_accepted('company_abc', 'admin')
> >
> > def geomorphix():
> >
> >     url='http://127.0.0.1:5006/bokeh'
>
> Same question as above: is that something that flask uses, or something
> that the web browser uses?
>
> Because the web browser will fail to access http://127.0.0.1:5006/
>
> > When I enter the website with the Bokeh script in my browser, I get a
> > connection refused error:
> >
> > GET http://127.0.0.1:5006/bokeh/example/autoload.js?bokeh-
> autoload-element=?
> > 9cf799610fb8&bokeh-session-id=8tvMFfJwtVFccTctGHIRPPsT3h6IF6
> nUFkJ8l6ZQALXl
> > net::ERR_CONNECTION_REFUSED
>
> That makes it look like the "url=" is something that the web browser uses.
>
> The web browser should only be accessing your https://nginx-server
> service, so urls that the web browser will use should refer to that.
>
> Possibly "url='/bokeh'" will Just Work for you.
>
> You mentioned the bokeh documentation at
>
> http://bokeh.pydata.org/en/latest/docs/user_guide/server.
> html#reverse-proxying-with-nginx-and-ssl
>
> and another link at
>
> http://stackoverflow.com/questions/38081389/bokeh-
> server-reverse-proxying-with-nginx-gives-404/38505205#38505205
>
> in your first mail. Does your current nginx configuration resemble either
> of those?
>
> Good luck with it,
>
>         f
> --
> Francis Daly        francis at daoine.org
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.nginx.org/pipermail/nginx/attachments/20170517/9c8234a2/attachment-0001.html>


More information about the nginx mailing list