Shiny

Shiny ist ein Web-Framework aus dem Hause Posit (ehemals RStudio), mit dem sich interaktive Webanwendungen bauen lassen, ohne dass man in JavaScript oder HTML schreibt. Für Eingaben (Slider, Dropdowns, Textfelder) und Ausgaben (Tabellen, Plots, berechneter Text) liefert das Framework Widgets; zwischen Eingabe und Ausgabe sitzt eine reaktive Berechnungs-Schicht, die bei Bedarf neu rendert. Ursprünglich 2012 für R vorgestellt, ist Shiny seit 2022 auch für Python verfügbar (pip install shiny, ASGI-basiert, unabhängig von der R-Implementierung gepflegt).

Eine klassische Shiny-App für R besteht aus zwei Blöcken, die entweder in einer app.R zusammen liegen oder auf ui.R und server.R aufgeteilt sind. ui.R definiert die Oberfläche, server.R die Logik und die Rendering-Funktionen. Mit Shinylive lässt sich eine Shiny-App ohne Server-Komponente direkt im Browser (WebAssembly) ausführen.

Das Shiny-Ökosystem

Posit vertreibt mehrere Produkte rund um Shiny. Die Abgrenzung ist wichtig, weil die Namen ähnlich klingen, die Rollen aber sehr verschieden sind:

Shiny

Das R- bzw. Python-Framework selbst. Open Source. Auf dem Entwickler-Rechner reicht es aus, um eine App interaktiv zu testen, und auch für einen einzelnen Produktions-Dienst lässt sich eine App mit shiny::runApp() bzw. shiny run hinter einem Reverse Proxy betreiben.

Shiny Server (Open Source)

Node.js-basierter Webserver, der mehrere R-Shiny-Apps auf einem Host hostet und pro Request einen R-Prozess startet. Konfiguration über /etc/shiny-server/shiny-server.conf. Bindet Shiny-Apps unter URL-Paths ein, rotiert Logs pro App. Posit bietet offizielle RPM-Builds nur für RHEL 8 an (sowie Ubuntu 20+ und SLES 15); für RHEL 9 und 10 gibt es kein offizielles Paket.

Shiny Server Pro

Die kommerzielle Variante mit LDAP/Active-Directory-Authentifizierung, pro App mehrere parallele R-Prozesse, Detail-Monitoring. Sales gestoppt per 2021-01-01, End-of-Life per 2026-03-31. Für Neukunden nicht mehr verfügbar, für Bestandskunden empfiehlt Posit die Migration auf Posit Connect.

Posit Connect

Kommerzielles Hauptprodukt von Posit und funktionaler Nachfolger von Shiny Server Pro. Hostet Shiny-Apps (R und Python), Plumber-/FastAPI-APIs, Jupyter-Notebooks, Quarto-Dokumente, R-Markdown-Reports und Streamlit-Apps auf einer Plattform, inklusive Benutzer- und Rechte-Management, Scheduling, Versionierung und Zugriffs-Audit.

Installation Shiny Server (Open Source) auf RHEL 8

Voraussetzungen und R installieren:

dnf --assumeyes install epel-release
dnf config-manager --set-enabled powertools
dnf --assumeyes install cmake tar wget R

R-Pakete für Shiny einrichten:

R --quiet -e "install.packages(c('shiny', 'rmarkdown', 'knitr', 'htmltools'), repos='https://cloud.r-project.org')"

Shiny Server herunterladen und installieren. Für die aktuellste Version die Download-Seite von Posit prüfen:

wget https://download3.rstudio.org/centos8/x86_64/shiny-server-1.5.23.1030-x86_64.rpm
dnf --assumeyes localinstall shiny-server-1.5.23.1030-x86_64.rpm

systemctl enable --now shiny-server
systemctl status shiny-server

Die Installation landet unter /opt/shiny-server; /srv/shiny-server ist ein Symlink darauf und soll als Site-Root dienen (Default-Config-Wert site_dir /srv/shiny-server). Beispiel-Apps liegen unter /srv/shiny-server/sample-apps/.

Erreichbar ist der Server danach auf http://<host>:3838/, die Beispiel-Apps unter http://<host>:3838/sample-apps/hello/ und http://<host>:3838/sample-apps/rmd/.

Bemerkung

Posit liefert keinen offiziellen Shiny-Server-Build für RHEL 9 oder 10. Wer auf eine neuere RHEL-Generation muss, hat drei Optionen: einen eigenen Build aus dem rstudio/shiny-server-Repo, den Betrieb im Container (z.B. über das offizielle Rocker-Image), oder die Umstellung auf Posit Connect bzw. shiny::runApp() hinter einem Reverse Proxy.

Basiskonfiguration

/etc/shiny-server/shiny-server.conf
# Run applications as the user "shiny"
run_as shiny;

# Server listening on port 3838
server {
    listen 3838;

    location / {
        site_dir /srv/shiny-server;
        log_dir /var/log/shiny-server;
        directory_index on;
    }
}

Nach jeder Änderung an der Konfiguration:

systemctl restart shiny-server

Einfache Shiny-Anwendung

/srv/shiny-server/sample-apps/hello/server.R
library(shiny)

# Define server logic required to draw a histogram
shinyServer(function(input, output) {

    # Expression that generates a histogram. The expression is
    # wrapped in a call to renderPlot to indicate that:
    #
    #   1) It is "reactive" and therefore should be automatically
    #      re-executed when inputs change
    #   2) Its output type is a plot

    output$distPlot <- renderPlot({
        x    <- faithful[, 2]  # Old Faithful Geyser data
        bins <- seq(min(x), max(x), length.out = input$bins + 1)

        # draw the histogram with the specified number of bins
        hist(x, breaks = bins, col = 'darkgray', border = 'white')
    })

})
/srv/shiny-server/sample-apps/hello/ui.R
library(shiny)

# Define UI for application that plots random distributions
shinyUI(pageWithSidebar(

    # Application title
    headerPanel("It's Alive!"),

    # Sidebar with a slider input for number of observations
    sidebarPanel(
        sliderInput("bins",
            "Number of bins:",
            min = 1,
            max = 50,
            value = 30)
    ),

    # Show a plot of the generated distribution
    mainPanel(
        plotOutput("distPlot", height = 250)
    )
))

Mandantentrennung über Apache Reverse Proxy

Shiny Server Open Source hat keine eingebaute Benutzer-Authentifizierung. Wer mehrere Apps mit unterschiedlichen Zugriffsrechten auf demselben Host betreibt, hat mehrere Optionen:

  • Separate Shiny-Server-Instanzen auf unterschiedlichen Ports oder IPs, je mit eigenem Reverse-Proxy-Vhost.

  • Je eine App pro Container.

  • Je eine VM pro Mandant.

  • Oder - die einfachste Variante - ein einzelner Shiny Server, der nur auf 127.0.0.1 lauscht, und davor ein Apache, der je Mandant einen VirtualHost mit Basic Auth und Proxy auf die passende Location bereitstellt.

Die letzte Variante im Detail: zwei Apps (hello und rmd), zwei Hostnames (shiny-hello.example.com und shiny-rmd.example.com), je eine .htpasswd-Datei pro Mandant.

Shiny-Server-Konfiguration - zwei getrennte Applikationen, nur lokal lauschend:

/etc/shiny-server/shiny-server.conf
run_as shiny;
preserve_logs true;

# listen locally only; Apache in front does the rest
server {
    listen 3838 127.0.0.1;

    location /hello {
        app_dir /srv/shiny-server/sample-apps/hello;
        log_dir /var/log/shiny-server/hello;
    }

    location /rmd {
        app_dir /srv/shiny-server/sample-apps/rmd;
        log_dir /var/log/shiny-server/rmd;
    }
}

Apache-VirtualHost für die App hello - die relevanten Teile:

<VirtualHost *:80>
    ServerName shiny-hello.example.com

    # Longer timeout so WebSocket connections don't drop after 10s
    Timeout 60

    # proxy_module
    ProxyPreserveHost On

    # rewrite_module
    RewriteEngine On

    # Restrict allowed HTTP methods
    RewriteCond %{REQUEST_METHOD} !^(GET|OPTIONS)
    RewriteRule .* - [redirect=405,last]

    <Location />
        AuthType Basic
        AuthName "Shiny App hello"
        AuthUserFile /etc/httpd/.htpasswd-hello
        Require valid-user
    </Location>

    # Upgrade WebSocket requests to ws://, everything else plain http://
    RewriteCond %{HTTP:Upgrade} =websocket [nocase]
    RewriteRule /(.*) ws://127.0.0.1:3838/hello/$1 [proxy,last]
    RewriteCond %{HTTP:Upgrade} !=websocket [nocase]
    RewriteRule /(.*) http://127.0.0.1:3838/hello/$1 [proxy,last]
</VirtualHost>

.htpasswd-Datei je Mandant anlegen:

htpasswd -c /etc/httpd/.htpasswd-hello firstname.lastname

Monitoring

Sinnvolle Checks für einen Shiny-Server-Host:

  • systemd-Unit shiny-server.service.

  • TCP-Port 3838 (bzw. die im Vhost konfigurierten).

  • HTTP-Response je Haupt-App (200, Response-Time).

Backup und Restore

Sichern:

  • /etc/shiny-server/: Server-Konfiguration.

  • /srv/shiny-server/: App-Code (wenn die Apps nicht ohnehin in Git liegen).

  • /var/log/shiny-server/ nur, wenn die App-Logs für eine Revision oder ein Audit wichtig sind.

Zum Restore Shiny Server neu installieren, Pakete und R-Module wieder einrichten, danach die Verzeichnisse zurückspielen.

Troubleshooting

App startet nicht, Fehler nur im Browser sichtbar

Default-mässig sind die Server-Logs in /var/log/shiny-server/ pro App-Session durchgeschrieben, werden aber nach erfolgreichem App-Start gelöscht. Für die Fehlersuche preserve_logs true; in shiny-server.conf setzen und den Dienst neu laden; ab dann bleiben die Logs liegen.

App-Log zeigt there is no package called '...'

Das R-Paket ist im System-R nicht installiert oder liegt in einer Library, die der shiny-User nicht lesen kann. Mit sudo --user=shiny R --quiet -e ".libPaths()" prüfen, welche Library-Pfade der Server-User wirklich sieht, und das fehlende Paket dort installieren.

App hängt im „greying out“-Zustand nach kurzer Zeit

WebSocket-Verbindung wurde abgeschnitten, typischerweise durch einen zu niedrigen Timeout im Reverse Proxy oder eine zwischengeschaltete Firewall. Apache-Vhost mit Timeout 60 oder höher konfigurieren.

App lokal auf dem Server starten, um Shiny-Server aus der Gleichung zu nehmen

Reproduziert das Problem auch ohne Shiny Server, liegt es in der App selbst:

sudo --user=shiny R --quiet -e "shiny::runApp('/opt/shiny-server/samples/sample-apps/hello')"
Logs unter /var/log/shiny-server/ aufräumen

Rotation über logrotate regeln. Die Datei /etc/logrotate.d/shiny-server wird vom Paket mitgeliefert; einzelne Log-Dateien lassen sich mit truncate --size=0 /var/log/shiny-server/<datei>.log leeren, ohne dass der Dienst die Handles verliert. Vom pauschalen rm -rf /var/log/shiny-server/* ist abzusehen - der Dienst schreibt dann teils ins Leere, bis er neu gestartet wurde, und die Mount-Point-Permissions können verlorengehen.