rcurllocalhostservinghttpuv

Access locally served files within an R session


Context

In order to test the web capabilities of an R package I am writing, I'm attempting to serve a file locally use the httpuv package so that I can run tests using an offline copy of the page.

Issue

However, curl doesn't seem to want to play nice with httpuv - specifically, when trying to read the hosted file using curl (for example, with curl::curl() or curl::curl_fetch_memory()), the request hangs, and eventually times out if not manually interrupted.

Minimal example

# Serve a small page
server <- httpuv::startServer("0.0.0.0", port = 9359, app = list(
  call = function(req) {
    list(
      status = 200L,
      headers = list("Content-Type" = "text/html"),
      body = "Some content..."
    )
  }
))

# Attempt to retrieve content (this hangs)
page <- curl::curl_fetch_memory(url = "http://127.0.0.1:9359")

httpuv::stopServer(server)

Current progress

Once the server has been started, running curl -v 127.0.0.1:9359 at the terminal returns content as expected. Additionally, if I open a new instance of RStudio and try to curl::curl_fetch_memory() in that new R session (while the old one is still open), it works perfectly.

Encouraged by that, I've been playing around with callr for a while, thinking maybe it's possible to launch the server in some background process, and then continue as usual. Unfortunately I haven't had any success so far with this approach.

Any insight or suggestions very much appreciated!


Solution

  • Isn't it a great feeling when you can come back and answer a question you asked!

    From the httpuv::startServer() documentation:

    startServer binds the specified port and listens for connections on an thread running in the background. This background thread handles the I/O, and when it receives a HTTP request, it will schedule a call to the user-defined R functions in app to handle the request. This scheduling is done with later(). When the R call stack is empty – in other words, when an interactive R session is sitting idle at the command prompt – R will automatically run the scheduled calls. However, if the call stack is not empty – if R is evaluating other R code – then the callbacks will not execute until either the call stack is empty, or the run_now() function is called. This function tells R to execute any callbacks that have been scheduled by later(). The service() function is essentially a wrapper for run_now().

    In other words, if we want to respond to requests as soon as they are received, we have to explicitly do so using httpuv::service(). Something like the following does the trick!

    s <- callr::r_session$new()
    on.exit(s$close())
    
    s$call(function() {
      httpuv::startServer("0.0.0.0", port = 9359, app = list(
        call = function(req) {
          list(
            status = 200L,
            headers = list("Content-Type" = "text/html"),
            body = "Some content...")
          )
        }
      ))
    
      while (TRUE) httpuv::service()
    })  
    
    # Give the server a chance to start
    Sys.sleep(3)
    page <- curl_fetch_memory(url = "http://127.0.0.1:9359")