Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Websocket support in Plumber #723

Open
jttoivon opened this issue Dec 14, 2020 · 10 comments
Open

Websocket support in Plumber #723

jttoivon opened this issue Dec 14, 2020 · 10 comments

Comments

@jttoivon
Copy link

Hi,

Does Plumber have any support for websockets? There seems to be support in httpuv, which
I suppose Plumber is based on. Or do I have to open another port with httpuv to listen for websocket
connections?

Jarkko

@meztez
Copy link
Collaborator

meztez commented Dec 14, 2020

What would be the use case? plumber does not support websockets at the moment.

    onWSOpen = function(ws){
      warning("WebSockets not supported.")
    },

@jttoivon
Copy link
Author

My server is performing long running computation (could be even hours), and I would like it
to send intermediate results to browser as the computation progresses.
This way the user sees some feedback in his browser.

I have understood that Websockets are the "correct" solution to this, and I have
managed to get it to work directly in httpuv (example below), but not through plumber.

Jarkko

library(httpuv)

s <- startServer("127.0.0.1", 8080,
  list(
    onWSOpen = function(ws) {
      # The ws object is a WebSocket object
      cat("Server connection opened.\n")

      ws$send("First message")
      Sys.sleep(1000)
      ws$send("Second message")
      Sys.sleep(1000)
      ws$send("Third message")

      ws$onClose(function() {
        cat("Server connection closed.\n")
      })
    }
  )
)

@meztez
Copy link
Collaborator

meztez commented Dec 14, 2020

@jttoivon What plumber feature are you looking to leverage in this case? Seem like httpuv would fit your need, no?

@jttoivon
Copy link
Author

Yes, maybe I should move from Plumber to httpuv. Currently I'm only using the http request parameter parsing, and
these to deliver static files:
#' @assets ../output /output
list()

#' @assets ../static /static
list()

And for serialisation of function return values.
I have to find out how to do these with httpuv.

Thanks!

@schloerke
Copy link
Collaborator

Going to reopen this to have more discussion... Since plumber is build on httpuv, this request seems reasonable.


For followup...

Due to this line... (specifically passing in self)

httpuv::runServer(host, port, self)
... we need to attach ws methods to the Plumber object directly.

This will not work due to Plumber being a locked class...

#' @plumber
function(pr) {
  pr$onWSOpen <- function(ws) { 
    # custom ws code here
  }
  pr
}
#> Error in pr$onWSOpen <- function(ws) { :
#>   cannot change value of locked binding for 'onWSOpen'

Even if Plumber$lock_class and Plumber$lock_objects set to FALSE, we still can not overwrite the method.


To get around this, maybe we could update the Plumber definition.

Plumber$onWSOpen <- function(ws) {
  if (private$ws_open) {
	private$ws_open(ws)
  }
  invisible(self)
}
# Same for onWSMessage and onWSClose

We could add them via

Plumber$websocket <- function(open, message, close) {
	private$ws_open <- open
	private$ws_message <- message
	private$ws_close <- close
}

(Plumber$ws does not feel descriptive enough)

In the end..

#' @plumber
function(pr) {
  pr$websocket(
    open = function(ws) {
      # custom code here
    }
  )
  pr
}

This even opens the door for Plumber$staticPaths/Plumber$staticPathOptions for httpuv to inherit.

@schloerke schloerke reopened this Dec 14, 2020
@meztez
Copy link
Collaborator

meztez commented Dec 14, 2020

I could not wrap my head around replicating plumber feature around websocket. If you have an idea, I could build a prototype.

I know grpc, http/2, tcp. I thought web sockets was more like a binary messaging system. Client and server had to know how to work with messages.

@schloerke
Copy link
Collaborator

Maybe we just start with the open method. The examples within httpuv do not utilize onWSMessage() or onWSClose().

If we could update this method:

plumber/R/plumber.R

Lines 757 to 759 in 24614d5

onWSOpen = function(ws){
warning("WebSockets not supported.")
},

To be :

    onWSOpen = function(ws) {
      if (private$ws_open) {
    	private$ws_open(ws)
      }
      invisible(self)
    },

and add the public method of

    websocket = function(open = NULL) {
      if (!is.null(open)) stopifnot(is.function(open))
      private$ws_open <- open
    }

and the private variable ws_open = NULL.


We should be able to test it by adding this to the plumber definition

#' @get /
function() { "running" }

#' @plumber
function(pr) {
  pr$websocket(
    function(ws) {
      print("It opened!") }
      # echo
      ws$onMessage(function(binary, message) {
        ws$send(message)
      })
    }
  )
}
plumb("plumber_issue_273.R") %>% pr_run(port = 8080)

Testing it with the example from the httpuv readme...

To test it out, you can connect to it using the websocket package (which provides a WebSocket client). You can do this from the same R process or a different one.

(We'll need to test using a different R process as plumber is blocking)

ws <- websocket::WebSocket$new("ws://127.0.0.1:8080/")
ws$onMessage(function(event) {
  cat("Client received message:", event$data, "\n")
})

# Wait for a moment before running next line
ws$send("hello world")

# Close client
ws$close()

(note: all codes are untested)

@schloerke schloerke added this to the v1.1.0 milestone Dec 17, 2020
@schloerke schloerke mentioned this issue Dec 30, 2020
3 tasks
@mskyttner
Copy link

Suggestions for use cases:

  • a use case reporting progress (and finally getting results from) a single long running calculation with gradual progression indication (much looking like what a progress bar does, or just a "% of completion figure"). This feels like what was previously mentioned by the original poster. An example would be to enable gradual progress indication from a plumber api utilizing progressr and furr in a calculation such as this one

  • a use case pushing several long running calculations to a task queue and monitoring the status of the queue, retrieving results when they are ready. For example such as in this attempt. That attempt needs some fixing or simplification due to a current issue <callr_status_error: callr subprocess failed: could not find function "my_pkg_fn"> in process, not sure why that happens.

@meztez
Copy link
Collaborator

meztez commented Jan 31, 2021

I'm pitching that out there. What about a type of endpoint for long running process that reports on progress when the endpoint is already executing? Deal with session? Is plumber trying to be grpc? Interesting to see how this will all evolve.

@vikram-rawat
Copy link

vikram-rawat commented Jul 7, 2021

It could also be beneficial for creating a chatbot session and much more...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants