Hello! I have found an interesting issue with the ShinyProxy Shiny for Python Demo: It does not work when ShinyProxy is behind a reverse-proxy: The static components load, but the WebSocket does not start. According to the container logs, the app itself is returning a 403 Forbidden
error. I’d appreciate some assistance in identifying exactly where the problem is.
I’m running ShinyProxy version 3.0.2-exec
, which I installed using the Debian package from the ShinyProxy downloads page.
Here's my ShinyProxy `application.yml` file:
proxy:
title: ShinyProxy!
logo-url: https://www.openanalytics.eu/shinyproxy/logo.png
bind-address: 0.0.0.0
landing-page: /
heartbeat-rate: 10000
heartbeat-timeout: 60000
port: 8080
authentication: simple
users:
- name: karl
password: karl
groups: admins
admin-groups: admins
docker:
port-range-start: 20000
image-pull-policy: always
specs:
- id: shinyproxy_python_demo
display-name: ShinyProxy Python Demo
description: The demo from https://github.com/openanalytics/shinyproxy-shiny-for-python-demo
container-image: openanalytics/shinyproxy-shiny-for-python-demo
port: 8080
server:
forward-headers-strategy: native
As you can see, I’m not making any changes to the container, and I’m not building it myself.
I mentioned that I’m using an Apache reverse-proxy. I have Apache set up to listen in ports 80 & 443. Requests to port 80 simply redirect to port 443.
Here's my Apache configuration for port 443
<VirtualHost *:443>
ServerAdmin srcc-support@stanford.edu
DocumentRoot /var/www/html
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
# Enable HTTP2 and 1.1
Protocols h2 http/1.1
# Enable SSL, with HSTS
SSLEngine on
SSLCertificateKeyFile /etc/ssl/private/apache2.key
SSLCertificateFile /etc/ssl/certs/bc-vm-hidplapps.pem
SSLCertificateChainFile /etc/ssl/certs/bc-vm-hidplapps.chain.pem
Header always set Strict-Transport-Security "max-age=63072000"
# Proxy requests through to ShinyProxy on port 8080
# NOTE: The WebSockets match must come first.
ProxyPassMatch "^/(.+)/websocket" "ws://127.0.0.1:8080/$1/websocket"
ProxyPass "/" "http://127.0.0.1:8080/"
ProxyPassReverse "/" "http://127.0.0.1:8080/"
ProxyPreserveHost On
RequestHeader set "X-Forwarded-Proto" https
I have Docker logs set to go to journald. Here’s what gets logged when I try to run the application:
[2024-06-13 21:50:58 +0000] [1] [INFO] Starting gunicorn 20.1.0
[2024-06-13 21:50:58 +0000] [1] [INFO] Listening at: http://0.0.0.0:8080 (1)
[2024-06-13 21:50:58 +0000] [1] [INFO] Using worker: uvicorn.workers.UvicornWorker
[2024-06-13 21:50:58 +0000] [7] [INFO] Booting worker with pid: 7
[2024-06-13 21:50:59 +0000] [7] [INFO] Started server process [7]
[2024-06-13 21:50:59 +0000] [7] [INFO] Waiting for application startup.
[2024-06-13 21:50:59 +0000] [7] [INFO] Application startup complete.
[2024-06-13 21:51:00 +0000] [7] [INFO] ('172.17.0.1', 34698) - "WebSocket /websocket" 403
[2024-06-13 21:51:00 +0000] [7] [INFO] connection failed (403 Forbidden)
[2024-06-13 21:51:00 +0000] [7] [INFO] connection closed
(edited to remove the stuff journald added to each line)
So, it seems to me like something in the Python app does not like that the request is coming in through a reverse-proxy.
You might ask, “How do you know the reverse-proxy is causing the problem?” You might also ask “Why is ShinyProxy listening on 0.0.0.0
instead of 127.0.0.1
?” The answers are related: I configured ShinyProxy to listen on all IPs, so that I could test trying to connect from my web browser to ShinyProxy directly on port 8080, bypassing Apache. If I connect to ShinyProxy directly—bypassing Apache & the reverse-proxy—the container works fine.
So, it seems to me like the problem is definitely something in the container, and that’s where I need help: I don’t know if the problem is in the Shiny for Python code, or in one of its dependencies.
I’d appreciate any pointers you can provide!