Search code examples
rshinyshiny-server

How sessions work in shiny-server?


I am having some troubles in understanding how sessions work in the shiny-server. I assume that a session finishes when the user close the browser, however, by using the print(session$isClosed()) in the server function I get a FALSE response at the beginning (so okay) and then when I close the browser nothing happens. Can anyone give me a clue about shiny-server sessions? I would want to store session specific plots to let the users download their plots only.


Solution

  • Well, to start with a shiny session object is a specific ('R6') data structure in shiny, made of public and private elements. It's purpose is to record one instance of the relationship between one user and shiny (more on this later).

    >str(session)
    Classes 'ShinySession', 'R6' <ShinySession>
      Public:
        @uploadEnd: function (jobId, inputId) 
        @uploadieFinish: function () 
        @uploadInit: function (fileInfos) 
        allowReconnect: function (value) 
        clientData: reactivevalues
        clone: function (deep = FALSE) 
        close: function () 
        closed: FALSE
        decrementBusyCount: function () 
        defineOutput: function (name, func, label) 
        dispatch: function (msg) 
        doBookmark: function () 
        downloads: Map, R6
        exportTestValues: function (..., quoted_ = FALSE, env_ = parent.frame()) 
        files: Map, R6
        fileUrl: function (name, file, contentType = "application/octet-stream") 
        flushOutput: function () 
        freezeValue: function (x, name) 
        getBookmarkExclude: function () 
        getTestEndpointUrl: function (inputs = TRUE, outputs = TRUE, exports = TRUE, format = "rds") 
        groups: NULL
        handleRequest: function (req) 
        incrementBusyCount: function () 
        initialize: function (websocket) 
        input: reactivevalues
        isClosed: function () 
        isEnded: function () 
        makeScope: function (namespace) 
        manageHiddenOutputs: function () 
        manageInputs: function (data) 
        ns: function (id) 
        onBookmark: function (fun) 
        onBookmarked: function (fun) 
        onEnded: function (endedCallback) 
        onFlush: function (flushCallback, once = TRUE) 
        onFlushed: function (flushedCallback, once = TRUE) 
        onInputReceived: function (callback) 
        onRestore: function (fun) 
        onRestored: function (fun) 
        onSessionEnded: function (sessionEndedCallback) 
        output: shinyoutput
        outputOptions: function (name, ...) 
        progressStack: environment
        reactlog: function (logEntry) 
        registerDataObj: function (name, data, filterFunc) 
        registerDownload: function (name, filename, contentType, func) 
        reload: function () 
        request: environment
        resetBrush: function (brushId) 
        restoreContext: RestoreContext, R6
        rootScope: function () 
        saveFileUrl: function (name, data, contentType, extra = list()) 
        sendBinaryMessage: function (type, message) 
        sendCustomMessage: function (type, message) 
        sendInputMessage: function (inputId, message) 
        sendInsertUI: function (selector, multiple, where, content) 
        sendModal: function (type, message) 
        sendNotification: function (type, message) 
        sendProgress: function (type, message) 
        sendRemoveUI: function (selector, multiple) 
        session: active binding
        setBookmarkExclude: function (names) 
        setShowcase: function (value) 
        showProgress: function (id) 
        singletons: 
        token: d44d583f13b3cd4ccce43f59fe410f61
        unhandledError: function (e) 
        updateQueryString: function (queryString) 
        user: NULL
        wsClosed: function () 
      Private:
        .clientData: ReactiveValues, R6
        .input: ReactiveValues, R6
        .outputOptions: list
        .outputs: list
        bookmarkCallbacks: environment
        bookmarkedCallbacks: environment
        bookmarkExclude: 
        busyCount: 2
        closedCallbacks: environment
        createBookmarkObservers: function () 
        enableTestEndpoint: function () 
        fileUploadContext: environment
        flushCallbacks: environment
        flushedCallbacks: environment
        getOutputOption: function (outputName, propertyName, defaultValue) 
        inputMessageQueue: list
        inputReceivedCallbacks: environment
        invalidatedOutputErrors: Map, R6
        invalidatedOutputValues: Map, R6
        outputValues: list
        progressKeys: character
        registerSessionEndCallbacks: function () 
        restoreCallbacks: environment
        restoredCallbacks: environment
        sendErrorResponse: function (requestMsg, error) 
        sendMessage: function (...) 
        sendResponse: function (requestMsg, value) 
        shouldSuspend: function (name) 
        showcase: FALSE
        storeOutputValues: function (values = NULL) 
        testEndpointUrl: session/d44d583f13b3cd4ccce43f59fe410f61/dataobj/shinyte ...
        testValueExprs: list
        websocket: WebSocket
        write: function (json) 
    

    A good way to explore the session object is to play with the shiny example in shiny gallery client-data-and-query-string. It allows to see what is contained for example in session$clientdata or any other element of the object.

    A couple of additional & misleadingly trivial points:

    • when does a session starts? When a user connects with the shiny app
    • when does a session ends? when a user disconnects with the shiny app

    As an example, to show how the issue is actually quite complex, if I refresh the browser, I end the present session and create a new one.

    Coming to session$isClosed(), this is not the right function to connect to specific action when a session is ended. This is actually the role of a shiny call back function

    onSessionEnded(fun, session = getDefaultReactiveDomain())
    

    A minimal example could be the following:

    library(shiny)
    
    ui =(
      fluidPage(
        titlePanel("This is an example")
      )
    )
    
    server = function(input, output, session){
      session$onSessionEnded({
        print("Stop!")
        stopApp   
      }) 
    }
    
    runApp(list(ui = ui, server = server))
    

    If you try, refreshing (or breaking up with browser() ) will print "Stop" and will stop the app.

    26 September 2017 Edit:

    In general, I think it is better to be cautious if the continuity of a session is of importance (and in any case it is appropriate to test session code directly on Shiny Server or Shiny Server Pro). Possibly the most important use cases come with Shiny Server Pro, where any disconnection may affect login status etc.).

    I'm also aware that the shiny team has made changes on these areas in recent versions. E.g., it seems that while onSessionEnded still works, possibly it is not anymore the best function for this usecase .

    See the following code as an example (from shiny reference guide), using onStop, that can work when a session ends, as well as when the app stops.

    library(shiny)
    
    cat("Doing application setup\n")
     onStop(function() {
       cat("Doing application cleanup\n")
     })
    
     shinyApp(
       ui = basicPage("onStop demo"),
    
       server = function(input, output, session) {
         onStop(function() cat("Session stopped\n"))
       }
     )