What is the best way of delivering static assets to the client for custom apps?


#1

Firstly, thanks so much for the efforts and open sourcing Shinyproxy.

Background

I deployed a Flask ‘hello world’ container on Shinyproxy which worked fine once I changed the ports to 3838. However, I’m now moving into deploying more complex python apps on Shinyproxy, namely dash apps. Dash was released a few months ago by Plotly claiming to be the ‘shiny of python’. This is quite exciting particularly if both Shiny and Dash can be deployed on Shinyproxy. The issue is dash apps require additional static resources. (Disclaimer!) I haven’t programmed in Java or SpringBoot. However, as far as I can see from looking at Chrome developer tools there are two ways Shinyproxy delivers static files to the client.

  1. Webjars - I can see that the main template of shinyproxy loads the bootstrap css which I think also gets utilised by each shiny app.

Possible Solution: Add all the static resources to the pom.xml and add them on the webjars.org site.

  1. Load from the Container - Additional static content for shiny apps is requested by using the container name, in this case peaceful_jepsen e.g.:
Request URL:http://<my-ip-address>/peaceful_jepsen/dt-core-1.10.12/css/jquery.dataTables.min.css

Possible Solution: Write Python code for the Dash app that obtains the container name and appends the path.

The Problem

When I deploy a Dash app the containerPath is not being inserted into the GET URL Request. Instead, it sends the request to the root. E.g.

`Request URL: http://<my-ip-address>/_dash-layout`

Questions

  • What is the best approach out of the above two methods for delivering static content to a non-shiny app? Or maybe there’s a better way.
  • Can I edit the application.yml configuration or source code to solve the problem?
  • Any recommended reading / documentation on how static files are being served for shinyproxy?

#2

Hi @Luke_Singham,

That’s an interesting question. You already made the distinction between content served by the shinyproxy server and content served by the web server inside the docker container.
I think it’s important to maintain that distinction, and have any content for your custom app served by the server inside your container. The shinyproxy server should only serve content related to shinyproxy itself, e.g. the login page, the app overview page, etc.

http://my-ip-address/peaceful_jepsen/

These URLs get proxied to your container and will be served from there.
For shiny apps, this works because all content referenced from the shiny app’s page uses hrefs that are relative to the page, e.g.

<link href="shared/shiny.css" rel="stylesheet" />

This ends up going to

http://my-ip-address/peaceful_jepsen/shared/shiny.css

I’m afraid I am not familiar with Dash, but if your URL ends up as

http://my-ip-address/_dash-layout

Then that means the href being used was “/_dash-layout” and not “_dash-layout”.
Do you control the hrefs? If so, removing the slash may solve your issue. If not, it becomes trickier… you could use javascript and extract the correct URL from window.location.

Regards,
Frederick


Shinyproxy and addResourcePath
#3

Hi Frederick,

Thanks for the quick response and insight provided. You’ve given me some ideas for things to try, particularly around how Flask.py (the Dash backend) is sending the requests. I’ll post back here with my updates :slight_smile:

Enjoy your weekend.
Luke


#4

I have tried changing the leading ‘/’ in Dash (Flask.py).

Attempt

server = flask.Flask(__name__)
app = dash.Dash(__name__, server=server, url_base_pathname='')

This breaks the app with the following error: ValueError: urls must start with a leading slash

Which is probably expected considering the property url_base_pathname of Dash is set to ‘/’.

Another Option
If I’m able to set the container name and pass that name as a variable to the containterised application, then I can prefix the routes Dash takes.

On my local

docker run -p 4002:3838 --name=this_name -e CONTAINER_NAME=this_name dash-image

Now that I have set the container name and set that name as a environment variable. My Flask/Dash app can prepend the requests.

container_name = os.environ.get('CONTAINER_NAME')

app.config.update({
    # as the proxy server will remove the prefix
    'routes_pathname_prefix': container_name + '/',

    # the front-end will prefix this string to the requests
    # that are made to the proxy server
    'requests_pathname_prefix': container_name + '/'
})

Result
This results in the GET requests looking like this:

- [07/Nov/2017 10:39:55] "GET /this_name/_dash-component-suites/dash_renderer/react@15.4.2.min.js?v=0.11.0 HTTP/1.1" 200 -

Question
Assuming there is no glaringly obvious reason this wouldn’t work, how do I set the docker image name to be custom in shinyproxy? And how do I pass an env var to the container? Can this be done in the application.yml?


#5

It’s working!

The solution was coded in the Dash code as follows:

# In order to work on shinyproxy
app.config.supress_callback_exceptions = True
app.config.update({
    # as the proxy server will remove the prefix
    'routes_pathname_prefix': ''

    # the front-end will prefix this string to the requests
    # that are made to the proxy server
    , 'requests_pathname_prefix': ''
})

Thanks @fmichielssen again for your help :slight_smile:


#6

Hi @Luke_Singham,

Good to hear, and thanks for sharing this information!


#7

@Luke_Singham,

There is now a full example of a Dash application to host on ShinyProxy at https://github.com/openanalytics/shinyproxy-dash-demo

Note that we also introduced a new port property that can be set per app (in ShinyProxy 1.1.0). This allows to use the default Dash port 8050 as in

shiny:
  
  ...
  
  apps:
  - name: dash-demo
    display-name: Dash Demo Application
    port: 8050
    docker-cmd: ["python", "app.py"]
    docker-image: openanalytics/shinyproxy-dash-demo
    ...

Hope this makes things a little easier!

Best,
Tobias