Open jttoivon opened 3 years ago
What would be the use case? plumber does not support websockets at the moment.
onWSOpen = function(ws){
warning("WebSockets not supported.")
},
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")
})
}
)
)
@jttoivon What plumber feature are you looking to leverage in this case? Seem like httpuv would fit your need, no?
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:
list()
list()
And for serialisation of function return values. I have to find out how to do these with httpuv.
Thanks!
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
)
https://github.com/rstudio/plumber/blob/24614d5085756a9ffb8ffbcda3adcdfdb1a238da/R/plumber.R#L214 ... 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.
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.
Maybe we just start with the open
method. The examples within httpuv
do not utilize onWSMessage()
or onWSClose()
.
If we could update this method: https://github.com/rstudio/plumber/blob/24614d5085756a9ffb8ffbcda3adcdfdb1a238da/R/plumber.R#L757-L759
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)
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.
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.
It could also be beneficial for creating a chatbot session and much more...
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