I have a working RPC TCP service written in Go, but when using Ruby to connect to the service it hangs because no data appears to be sent back through the open socket connection.
Remote RPC Function:
package remote
import "fmt"
// Compose is our RPC functions return type
type Compose string
// Details is our exposed RPC function
func (c *Compose) Details(arg string, reply *string) error {
fmt.Printf("Arg received: %+v
", arg)
*c = "some value"
*reply = "Blah!"
return nil
}
Remote RPC Endpoint Exposed:
package remote
import (
"fmt"
"net"
"net/rpc"
)
// Endpoint exposes our RPC over TCP service
func Endpoint() {
compose := new(Compose)
rpc.Register(compose)
listener, err := net.Listen("tcp", ":8080")
// error handling omitted
for {
conn, err := listener.Accept()
// error handling omitted
go rpc.ServeConn(conn)
}
}
Client Connection over TCP to Remote RPC function (using Golang so I know it works at least in that respect):
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
client, err := rpc.Dial("tcp", "localhost:8080")
if err != nil {
log.Fatal("dialing:", err)
}
var reply string
e := client.Call("Compose.Details", "my string", &reply)
if e != nil {
log.Fatalf("Something went wrong: %v", e.Error())
}
fmt.Printf("The 'reply' pointer value has been changed to: %s", reply)
}
Here is the Ruby code I'm attempting to use:
require "socket"
require "json"
socket = TCPSocket.new "localhost", "8080"
b = {
method: "Compose.Details",
params: "foobar"
}
socket.write(JSON.dump(b))
resp = JSON.load(socket.readline)
p resp
The reason I think it doesn't work is because the Go RPC requires a pointer to be provided as the second argument to the exposed RPC function but I'm not sure how that works in a different programming language such as Ruby?
I've also tried utilising a library such as https://github.com/chriskite/jimson
require "jimson"
client = Jimson::Client.new("localhost:8080")
result = client.Details("foobar")
But all I get back is:
RestClient::ServerBrokeConnection: Server broke connection
UPDATE
So as indicated by JimB in the comments, I needed to use the JSON RPC library instead. Things still don't quite work, as you'll see...
Here's the updated Ruby client:
require "socket"
require "json"
socket = TCPSocket.new "localhost", "8080"
b = {
method: "Compose.Details",
params: [{ :args => { :foo => "Foo!", :bar => "Bar!" }, :id => "0" }]
}
socket.write(JSON.dump(b))
p JSON.load(socket.readline)
Followed by the updated Go client:
package remote
import "fmt"
// Args is structured around the client's provided parameters
type Args struct {
foo string
bar string
}
// Compose is our RPC functions return type
type Compose string
// Details is our exposed RPC function
func (c *Compose) Details(args *Args, reply *string) error {
fmt.Printf("Args received: %+v
", args)
*c = "some value"
*reply = "Blah!"
return nil
}
When I run this my Ruby client sees the following:
{"id"=>nil, "result"=>"Blah!", "error"=>nil}
So I'm getting a result back, but if my actual RPC code tried to use the provided args in any way to generate the result then this would be a broken example because the stdout I get from the Go RPC function is a set of empty arg values?
Args received: &{foo: bar:}
I'm not sure why the RPC function isn't able to pull in the arguments I've provided to it?
Also the "id" key: why would I get one back from the RPC function? and how can I give it a meaningful value?
I tried setting an "id" key with a value from my Ruby client but that didn't effect the response from the Go RPC.