SciViews/svSocket

Need an example of a custom procfun

Opened this issue · 5 comments

I'm trying to write a custom server. The input and output are large JSON objects, so I thought the best solution was to write a custom procfun so I didn't need to worry about escaping quotes.

Here is my test code:

library(svSocket)

processTestSocket <-
function (msg, socket, serverport, ...) 
{
    cat("Message was:",msg,"\n")
    output <- paste0("message:",msg)
    return(output)
}

options(debug.Socket=TRUE)
startSocketServer(port=12525,server.name="EAServer",
                  procfun=processTestSocket)

This returns TRUE in the console.

However, when I try to test the port using telnet, it closes immediately.

$ telnet localhost 12525
Trying ::1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
$ sudo ss -l |fgrep 12525
tcp   LISTEN 0      4096                                                                                  0.0.0.0:12525                      0.0.0.0:*          
tcp   LISTEN 0      4096                                                                                     [::]:12525                         [::]:*          
$

What am I missing?

Thanks for the pointer to plumber. It doesn't solve this problem, but it might mean I can work in R instead of php for some other web apps I'm working on.

A little bit more about the problem.
I'm working on a scoring server for a complex assessment.

The R server needs to
a) Set up a rather complex scoring model.
b) Process the data for each student.
because of (a), I would like it to stay alive and not reload everything to score each student (this is why plumber won't work).

I have another application running on the same machine that stores the student data. My design was to write a small php script that extracted the relevant data from the student record, sent the relevant data (as JSON) to the scoring server, which would respond with the scores (also as JSON) back to the script that would insert the socres into the student record and return it.

The client script is written in php, but I found an example connecting to svSocket from php, so that is fine.

Your code also solves another problem I have with just using raw socketConnection calls in R, there I get all kinds of timeout problems.

The scoring function is a single command, so there is no problem there. The issue with using svSocket is that if I need to pass the JSON as a long string argument, I will need to escape all of the quotes. There is probably an easy way to do that in php, but I don't know it.

I think I could write a simple echo-back procfun, I could easily replace it with my scoring code.

My use of telnet is just a easy way to test the connection.

I think I have gotten this working.

The key challenge on my part was dealing with the embedded quotes in the JSON string. Using raw syntax r"(...)", or as )" might be a valid data sequence r"---(...)---". Then I wrap the command in print(...,quote=FALSE) to suppress the escaping of quotes in the response.

So my php code looks like this:

// This gets around issues where feof might hang.
function safe_feof($fp, &$start = NULL) {
 $start = microtime(true);
 return feof($fp);
}

//Reads from $fp until `\r\n' and strips the `[1]` from the string.
 function fget_allR($fp) {
     $result= "";
     $start=NULL;
     while (!safe_feof($fp,$start) &&
               (microtime(true) - $start) < self::SOCKET_TIMEOUT) {
         $line = fgets($fp);
         if (str_starts_with($line,"\r\n")) break;
         $result .= $line;
     }
     return substr($result,3);
}

//Finally, the principle method which does the work:
    function process_message_svRaw($input_mess) {
        $input_string = json_encode($input_mess,
            JSON_UNESCAPED_UNICODE | 
            JSON_UNESCAPED_SLASHES);

        //Request that R process the file through svSocket
        $socket = fsockopen ($this->host, $this->port, 
                                           $errno, $errstr,300) or
                die ("Error opening socket: $errstr.\n");
        $in = 'print(' .self::RCMD. '(r"---('.$input_string.')---"), 
                           quote=FALSE)';

        fwrite(STDERR,"Message to R:".$in."\n");
        fputs($socket,"$in\r") or die("Error writing to svSocket.");
        //$reply = fgets($socket);
        $reply=$this->fget_allR($socket);
        fwrite(STDERR,"Reply from R:".$reply."\n");
        fclose($socket);

        return json_decode($reply,true);
    }

This seems to work (particularly, when I set the self::RCMD constant to "identity" for testing).

I don't need to deal with the multiple lines of input code, although I'm not sure whether or not json_encode() always returns a single line, so it is comforting to know the svSocket should deal with them.

By trial and error, I've determined that the reply is followed by a line starting with \r\n'. It would be nice to replace that with a unique string. I think that according to the docs, I can set this with the parSocket(...,last="ThisIsTheEnd.\n")or something similar, but I'm not quite sure what the...` used to identify my socket should be. (The example only works with a fake socket).
I'm starting the socket with the (R) command:

startSocketServer(port=12525,server.name="EAServer")

@ralmond Yes, this is it (note that I have not tested your code, but it seems valid to me). Regarding the multiline feature of {svSocket}, you don't have to worry about it: if you always submit a complete R code, you will never hit it.
For the text you want as a marker for the end of result, yes, you specify it with the last= argument.