WebSocket does not reconnect

This issue is also opened on shinyxproxy’sgithub: https://github.com/openanalytics/shinyproxy/issues/362

Context

Hi,

We are using shiny proxy behind a reverse proxy as displayed in the following chart. All of this is deployed over Kubernetes.

flowchart LR
  User--connect to-->Reverse-proxy;
  Reverse-proxy--redirect-->app1[Some Shiny App];
  Reverse-proxy--redirect-->ShinyProxy;
  ShinyProxy--manage sessions-->app2[Another App];

We are experiencing issues with our WebSocket when deploying our shinyapp using shinyproxy. The WebSocket is instantiated once and does not reconnect afterward. In other words, after a few minutes, we got the gray layer appears over the app indicating that the connection is lost.

Two addtional informations:

  • The heartbeat still works fine.
  • In the first branch scenario i.e. without shinyproxy, the WebSocket reconnects normally. Both use the same nginx configuration.

First, we thought that it could be duet to a timeout settings but we cannot find any fitting the time.

Do you have any idea, please?

Thankfully,

VL

Expected behavior

WebSocket should reconnect preventing from having an abrupt deconnection

Details

shinyproxy configuration

server:
  useForwardHeaders: true
  servlet:
    context-path: /myexplorerbis

proxy:
  title: MyExplorer-Launcher
  logo-url: https://devmystat.lorealri.com/home/resources/imgs/logo-mini.jpg
  landing-page: /myexplorerbis/app/main
  heartbeat-rate: 10000
  heartbeat-timeout: 60000
  port: 8080
  authentication: none
  hide-navbar: true
  container-backend: kubernetes
  kubernetes:
    namespace: mystat-myexplorerbis
    api-version: v1
    image-pull-policy: IfNotPresent
    privileged: false
    internal-networking: true
    container-protocol: http
    port: 3838
    pod-wait-time: 120000
  specs:
  - id: main
    display-name: MyExplorer Application
    container-cmd: ["Rscript", "/srv/app.R"]
    container-image: xxxxx/myexplorer:0.6.1.1-shinyproxy
    container-memory-request: 3Gi
    container-memory-limit: 3Gi
    container-cpu-request: 1
    container-cpu-limit: 1
    port: 3838
    stop-on-logout: true
    max-lifetime: 120

logging:
  file:
    name: shinyproxy.log

Nginx server configuration

location /myexplorerbis/ {

                        set $namespace      "mystat-myexplorerbis";
                        set $ingress_name   "application-ingress";
                        set $service_name   "application-svc";
                        set $service_port   "80";
                        set $location_path  "/myexplorerbis/";
                        set $global_rate_limit_exceeding n;

                        rewrite_by_lua_block {
                                lua_ingress.rewrite({
                                        force_ssl_redirect = false,
                                        ssl_redirect = true,
                                        force_no_ssl_redirect = false,
                                        use_port_in_redirects = false,
                                global_throttle = { namespace = "", limit = 0, window_size = 0, key = { }, ignored_cidrs = { } },
                                })
                                balancer.rewrite()
                                plugins.run()
                        }

                        # be careful with `access_by_lua_block` and `satisfy any` directives as satisfy any
                        # will always succeed when there's `access_by_lua_block` that does not have any lua code doing `ngx.exit(ngx.DECLINED)`
                        # other authentication method such as basic auth or external auth useless - all requests will be allowed.
                        #access_by_lua_block {
                        #}

                        header_filter_by_lua_block {
                                lua_ingress.header()
                                plugins.run()
                        }

                        body_filter_by_lua_block {
                                plugins.run()
                        }

                        log_by_lua_block {
                                balancer.log()

                                monitor.call()

                                plugins.run()
                        }

                        port_in_redirect off;

                        set $balancer_ewma_score -1;
                        set $proxy_upstream_name "mystat-myexplorerbis-application-svc-80";
                        set $proxy_host          $proxy_upstream_name;
                        set $pass_access_scheme  $scheme;

                        set $pass_server_port    $server_port;

                        set $best_http_host      $http_host;
                        set $pass_port           $pass_server_port;

                        set $proxy_alternative_upstream_name "";

                        client_max_body_size                    0;

                        proxy_set_header Host                   $best_http_host;

                        # Pass the extracted client certificate to the backend

                        # Allow websocket connections
                        proxy_set_header                        Upgrade           $http_upgrade;

                        proxy_set_header                        Connection        $connection_upgrade;

                        proxy_set_header X-Request-ID           $req_id;
                        proxy_set_header X-Real-IP              $remote_addr;

                        proxy_set_header X-Forwarded-For        $remote_addr;

                        proxy_set_header X-Forwarded-Host       $best_http_host;
                        proxy_set_header X-Forwarded-Port       $pass_port;
                        proxy_set_header X-Forwarded-Proto      $pass_access_scheme;

                        proxy_set_header X-Scheme               $pass_access_scheme;

                        # Pass the original X-Forwarded-For
                        proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;

                        # mitigate HTTPoxy Vulnerability
                        # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
                        proxy_set_header Proxy                  "";

                        # Custom headers to proxied server

                        proxy_connect_timeout                   5s;
                        proxy_send_timeout                      3600s;
                        proxy_read_timeout                      3600s;

                        proxy_buffering                         off;
                        proxy_buffer_size                       4k;
                        proxy_buffers                           4 4k;

                        proxy_max_temp_file_size                1024m;

                        proxy_request_buffering                 on;
                        proxy_http_version                      1.1;

                        proxy_cookie_domain                     off;
                        proxy_cookie_path                       off;

                        # In case of errors try the next upstream server before returning an error
                        proxy_next_upstream                     error timeout;
                        proxy_next_upstream_timeout             0;
                        proxy_next_upstream_tries               3;

                        proxy_pass http://upstream_balancer;

                        proxy_redirect                          off;

                }

                location = /myexplorerbis/ {

                        set $namespace      "mystat-myexplorerbis";
                        set $ingress_name   "application-ingress";
                        set $service_name   "application-svc";
                        set $service_port   "80";
                        set $location_path  "/myexplorerbis/";
                        set $global_rate_limit_exceeding n;

                        rewrite_by_lua_block {
                                lua_ingress.rewrite({
                                        force_ssl_redirect = false,
                                        ssl_redirect = true,
                                        force_no_ssl_redirect = false,
                                        use_port_in_redirects = false,
                                global_throttle = { namespace = "", limit = 0, window_size = 0, key = { }, ignored_cidrs = { } },
                                })
                                balancer.rewrite()
                                plugins.run()
                        }

                        # be careful with `access_by_lua_block` and `satisfy any` directives as satisfy any
                        # will always succeed when there's `access_by_lua_block` that does not have any lua code doing `ngx.exit(ngx.DECLINED)`
                        # other authentication method such as basic auth or external auth useless - all requests will be allowed.
                        #access_by_lua_block {
                        #}

                        header_filter_by_lua_block {
                                lua_ingress.header()
                                plugins.run()
                        }

                        body_filter_by_lua_block {
                                plugins.run()
                        }

                        log_by_lua_block {
                                balancer.log()

                                monitor.call()

                                plugins.run()
                        }

                        port_in_redirect off;

                        set $balancer_ewma_score -1;
                        set $proxy_upstream_name "mystat-myexplorerbis-application-svc-80";
                        set $proxy_host          $proxy_upstream_name;
                        set $pass_access_scheme  $scheme;

                        set $pass_server_port    $server_port;

                        set $best_http_host      $http_host;
                        set $pass_port           $pass_server_port;

                        set $proxy_alternative_upstream_name "";

                        client_max_body_size                    0;

                        proxy_set_header Host                   $best_http_host;

                        # Pass the extracted client certificate to the backend

                        # Allow websocket connections
                        proxy_set_header                        Upgrade           $http_upgrade;

                        proxy_set_header                        Connection        $connection_upgrade;

                        proxy_set_header X-Request-ID           $req_id;
                        proxy_set_header X-Real-IP              $remote_addr;

                        proxy_set_header X-Forwarded-For        $remote_addr;

                        proxy_set_header X-Forwarded-Host       $best_http_host;
                        proxy_set_header X-Forwarded-Port       $pass_port;
                        proxy_set_header X-Forwarded-Proto      $pass_access_scheme;

                        proxy_set_header X-Scheme               $pass_access_scheme;

                        # Pass the original X-Forwarded-For
                        proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;

                        # mitigate HTTPoxy Vulnerability
                        # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
                        proxy_set_header Proxy                  "";

                        # Custom headers to proxied server

                        proxy_connect_timeout                   5s;
                        proxy_send_timeout                      3600s;
                        proxy_read_timeout                      3600s;

                        proxy_buffering                         off;
                        proxy_buffer_size                       4k;
                        proxy_buffers                           4 4k;

                        proxy_max_temp_file_size                1024m;

                        proxy_request_buffering                 on;
                        proxy_http_version                      1.1;

                        proxy_cookie_domain                     off;
                        proxy_cookie_path                       off;

                        # In case of errors try the next upstream server before returning an error
                        proxy_next_upstream                     error timeout;
                        proxy_next_upstream_timeout             0;
                        proxy_next_upstream_tries               3;

                        proxy_pass http://upstream_balancer;

                        proxy_redirect                          off;

                }


Specific backend configuration

  {
    "name": "mystat-myexplorerbis-application-svc-80",
    "service": {
      "metadata": {
        "creationTimestamp": null
      },
      "spec": {
        "ports": [
          {
            "name": "http",
            "protocol": "TCP",
            "port": 80,
            "targetPort": 8080
          }
        ],
        "selector": {
          "applabel": "deployment-myexplorerbis"
        },
        "clusterIP": "X.X.X.X",
        "type": "ClusterIP",
        "sessionAffinity": "None"
      },
      "status": {
        "loadBalancer": {}
      }
    },
    "port": 80,
    "sslPassthrough": false,
    "endpoints": [
      {
        "address": "X.X.X.X",
        "port": "8080"
      }
    ],
    "sessionAffinityConfig": {
      "name": "cookie",
      "mode": "persistent",
      "cookieSessionAffinity": {
        "name": "mystat-myexplorerbis-cookie",
        "expires": "172800",
        "maxage": "172800",
        "locations": {
          "nginx-ingress-ingress-nginx-controller.ingress.svc.cluster.local": [
            "/myexplorerbis/"
          ]
        }
      }
    },
    "upstreamHashByConfig": {
      "upstream-hash-by-subset-size": 3
    },
    "noServer": false,
    "trafficShapingPolicy": {
      "weight": 0,
      "header": "",
      "headerValue": "",
      "headerPattern": "",
      "cookie": ""
    }
  }