openanalytics / shinyproxy

ShinyProxy - Open Source Enterprise Deployment for Shiny and data science apps
https://www.shinyproxy.io
Apache License 2.0
525 stars 151 forks source link

Special characters in HTTP headers break an application within shinyproxy #533

Open LDSamson opened 1 month ago

LDSamson commented 1 month ago

To reproduce:

Now, the app named 'Hello Application' cannot be started anymore.

I stumbled upon this issue when users with special characters in their name tried to start an application but failed to do so. In our production version, I am extracting the name (witrh special characters) from an OpenID connect claim, but below shows the issue also occurs in shinyproxy with the demo shiny applications and simple login.

These are the ShinyProxy error logs:

2024-10-25T15:05:50.668Z  INFO 1 --- [ProxyService-16] e.o.containerproxy.service.ProxyService  : [user=jack proxyId=63da50cb-eda5-4fd6-a2fa-7b3a53d854e0 specId=01_hello] Proxy activated
2024-10-25T15:05:50.726Z ERROR 1 --- [   XNIO-1 I/O-1] io.undertow.proxy                        : UT005028: Proxy request to /proxy_endpoint/63da50cb-eda5-4fd6-a2fa-7b3a53d854e0/ failed

java.io.IOException: UT001000: Connection closed
        at io.undertow.client.http.HttpClientConnection$ClientReadListener.handleEvent(HttpClientConnection.java:600) ~[undertow-core-2.3.13.Final.jar!/:2.3.13.Final]
        at io.undertow.client.http.HttpClientConnection$ClientReadListener.handleEvent(HttpClientConnection.java:535) ~[undertow-core-2.3.13.Final.jar!/:2.3.13.Final]
        at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) ~[xnio-api-3.8.8.Final.jar!/:3.8.8.Final]
        at org.xnio.conduits.ReadReadyHandler$ChannelListenerHandler.readReady(ReadReadyHandler.java:66) ~[xnio-api-3.8.8.Final.jar!/:3.8.8.Final]
        at org.xnio.nio.NioSocketConduit.handleReady(NioSocketConduit.java:89) ~[xnio-nio-3.8.8.Final.jar!/:3.8.8.Final]
        at org.xnio.nio.WorkerThread.run(WorkerThread.java:591) ~[xnio-nio-3.8.8.Final.jar!/:3.8.8.Final]

2024-10-25T15:05:50.741Z ERROR 1 --- [   XNIO-1 I/O-1] io.undertow.proxy                        : UT005028: Proxy request to /proxy_endpoint/63da50cb-eda5-4fd6-a2fa-7b3a53d854e0/ failed

java.nio.channels.ClosedChannelException: null
        at io.undertow.client.http.HttpClientConnection$5.handleEvent(HttpClientConnection.java:194) ~[undertow-core-2.3.13.Final.jar!/:2.3.13.Final]
        at io.undertow.client.http.HttpClientConnection$5.handleEvent(HttpClientConnection.java:177) ~[undertow-core-2.3.13.Final.jar!/:2.3.13.Final]
        at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) ~[xnio-api-3.8.8.Final.jar!/:3.8.8.Final]
        at org.xnio.StreamConnection.invokeCloseListener(StreamConnection.java:132) ~[xnio-api-3.8.8.Final.jar!/:3.8.8.Final]
        at org.xnio.Connection.close(Connection.java:142) ~[xnio-api-3.8.8.Final.jar!/:3.8.8.Final]
        at org.xnio.IoUtils.safeClose(IoUtils.java:152) ~[xnio-api-3.8.8.Final.jar!/:3.8.8.Final]
        at io.undertow.util.ConnectionUtils.doDrain(ConnectionUtils.java:90) ~[undertow-core-2.3.13.Final.jar!/:2.3.13.Final]
        at io.undertow.util.ConnectionUtils.cleanClose(ConnectionUtils.java:74) ~[undertow-core-2.3.13.Final.jar!/:2.3.13.Final]
        at io.undertow.client.http.HttpClientConnection.close(HttpClientConnection.java:491) ~[undertow-core-2.3.13.Final.jar!/:2.3.13.Final]
        at org.xnio.IoUtils.safeClose(IoUtils.java:152) ~[xnio-api-3.8.8.Final.jar!/:3.8.8.Final]
        at io.undertow.client.http.HttpClientConnection$ClientReadListener.handleEvent(HttpClientConnection.java:605) ~[undertow-core-2.3.13.Final.jar!/:2.3.13.Final]
        at io.undertow.client.http.HttpClientConnection$ClientReadListener.handleEvent(HttpClientConnection.java:535) ~[undertow-core-2.3.13.Final.jar!/:2.3.13.Final]
        at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) ~[xnio-api-3.8.8.Final.jar!/:3.8.8.Final]
        at org.xnio.conduits.ReadReadyHandler$ChannelListenerHandler.readReady(ReadReadyHandler.java:66) ~[xnio-api-3.8.8.Final.jar!/:3.8.8.Final]
        at org.xnio.nio.NioSocketConduit.handleReady(NioSocketConduit.java:89) ~[xnio-nio-3.8.8.Final.jar!/:3.8.8.Final]
        at org.xnio.nio.WorkerThread.run(WorkerThread.java:591) ~[xnio-nio-3.8.8.Final.jar!/:3.8.8.Final]

2024-10-25T15:05:50.745Z ERROR 1 --- [   XNIO-1 I/O-1] io.undertow.proxy                        : UT005028: Proxy request to /proxy_endpoint/63da50cb-eda5-4fd6-a2fa-7b3a53d854e0/ failed

java.io.IOException: UT001000: Connection closed
        at io.undertow.client.http.HttpClientConnection$ClientReadListener.handleEvent(HttpClientConnection.java:600) ~[undertow-core-2.3.13.Final.jar!/:2.3.13.Final]
        at io.undertow.client.http.HttpClientConnection$ClientReadListener.handleEvent(HttpClientConnection.java:535) ~[undertow-core-2.3.13.Final.jar!/:2.3.13.Final]
        at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) ~[xnio-api-3.8.8.Final.jar!/:3.8.8.Final]
        at org.xnio.conduits.ReadReadyHandler$ChannelListenerHandler.readReady(ReadReadyHandler.java:66) ~[xnio-api-3.8.8.Final.jar!/:3.8.8.Final]
        at org.xnio.nio.NioSocketConduit.handleReady(NioSocketConduit.java:89) ~[xnio-nio-3.8.8.Final.jar!/:3.8.8.Final]
        at org.xnio.nio.WorkerThread.run(WorkerThread.java:591) ~[xnio-nio-3.8.8.Final.jar!/:3.8.8.Final]

2024-10-25T15:05:50.803Z  INFO 1 --- [   XNIO-1 I/O-1] e.o.c.util.ProxyMappingManager           : [user=jack proxyId=63da50cb-eda5-4fd6-a2fa-7b3a53d854e0 specId=01_hello] Failed request: GET http://localhost:8080/proxy_endpoint/63da50cb-eda5-4fd6-a2fa-7b3a53d854e0/ was proxied to: http://2b650140c42a:3838/, status: 503
LEDfan commented 4 weeks ago

Hi, as far as I know HTTP headers may not contain all UTF-8 characters, but only ASCII characters, see https://stackoverflow.com/questions/47687379/what-characters-are-allowed-in-http-header-values/48138818#48138818 . One way to work-around is to base64 encode the value (see https://shinyproxy.io/documentation/spel/#tips--tricks) :

  - id: 01_hello
    display-name: Hello Application
    description: Application which demonstrates the basics of a Shiny app
    container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"]
    container-image: openanalytics/shinyproxy-demo
    container-network: sp-example-net
    http_headers:
        test_header: "#{T(java.util.Base64).getEncoder().encodeToString('ěšťůšěř Főřáťěš'.getBytes())}"
LDSamson commented 3 weeks ago

Thanks for providing this workaround, this indeed helps with this problem and I will apply it to our application in production.

In the long term, would it be possible to make shinyproxy a bit more robust for these cases and, for example, create informative error messages if shinyproxy is asked to send non-ascii characters as http headers? Or, send a warning that the string is an invalid http header and send an empty string instead? I think this would be helpful for many shinyproxy users if their names consist characters less commonly used in English. If this is difficult to achieve, a small warning in the documentation about this problem would also already be helpful.

In my use case, my application will display a user name but will fall back to the email address if the user name is not available.

LEDfan commented 2 weeks ago

Yes, I agree ShinyProxy could handle this better. I created and internal ticket and we will improve this in the next release. In the meantime I added an example to the demo https://github.com/openanalytics/shinyproxy-shiny-demo-auth?tab=readme-ov-file#utf-8-characters and a note on the website: https://shinyproxy.io/documentation/configuration/#http-headers