cothority
cothority copied to clipboard
make OCS endpoints available via REST/JSON
ByzGen asked whether it would be possible to have a more standard client-interface:
- gRPC - would be quite close to what we already have
- REST/JSON - would open up a bigger set of possible interactions
Any thoughts on feasibility of these? Either on the standard port, or on yet another port?
Initial investigation results:
gRPC is similar to protobuf except that it additionally generates functions for making the RPC call. What you write are messages and service definitions like this:
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Then it'll generate functions which you can use to create clients and servers, and send messages. For example:
// CLIENT
c := pb.NewGreeterClient(conn)
// setup context
c.SayHello(ctx, &pb.HelloRequest{Name: "hello world"})
// SERVER
type server struct{}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.Name)
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
// register server struct to the gRPC server
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
Just like protobuf, the right way to use gRPC is write these messages and definitions in .proto
files and then generate client and server code in various programming languages. However, this is not the model that cothority is using so we need to think of another way of using gRPC.
If we stick to our current approach, we need to generate gRPC-style .proto
files, like service Greeter
in the example above. Which means we need to parse our service handlers. We also need a translation layer and gRPC server in onet that converts requests to gRPC to the appropriate handlers.
My current feeling is that we shouldn't invest in more non-standard tech, that is, generating gRPC protobuf files from go source code.
My current feeling is that we shouldn't invest in more non-standard tech, that is, generating gRPC protobuf files from go source code.
Yes, definitely. I already though what would be needed to do the 'correct' .proto -> go conversion. In fact, we could probably make it quite painless by simply embedding the created protobuf-structures in our own. The only downside would be that it would not correctly parse dependant structures: so if you have a onet.Roster
field, I don't know how you could turn it into a onet.Roster
instead of a protobuf.Roster
. In Javascript, Gaylor wrote nice wrapper-methods that allow to do it. But I don't know if this is possible in go.
Following a discussion with @ineiti and @jeffallen, we will begin experimenting with JSON over REST. onet will expose an additional handler registration method, e.g., RegisterRESTHandler(version int, handler interface{})
. The version number is the REST API version. The handler follows the same form as before: func(msg interface{})(ret interface{}, err error)
.
Clients make requests (probably only POST is accepted) to https://example.com/$api_version/$service_name/$struct_name
to use the REST API. The request is converted into a regular Go object and then the appropriate handler is called. The endpoint returns a 200 OK with a payload if the request is successful. Otherwise an appropriate error code (404 if the JSON is invalid and 5xx for everything else).
Open questions:
- Should we use POST for all request?
- We're using REST but we're not following any of the REST conventions, is that ok?
- We upgrade http.Requests to websocket requests. The upgrade fails if the request headers are wrong. So another way that can help decide whether we're using REST or websocket is to switch on the header. If the upgrade fails then we'll treat it as a REST request and interpret the payload as a JSON object. I'm not sure about the pros/cons. (EDIT: actually, this approach might be too complicated, it's easier to just write a new
Handler
interface for the REST API) - What error codes should we use? There will be two forms of errors, one that comes from the handler and the other comes from the websocket/REST server. Unless we change the return type of the handler, it's difficult to specify precisely which error code to use.
first experiment: https://github.com/dedis/onet/commit/ba58c642cb374fd905a202944a052424c5095cba
Running something like curl --data '{"Version": 1, "Key": "ZW5jb2Rpbmc=", "ID": "ZW5jb2Rpbmc="}' http://127.0.0.1:7771/v3/ByzCoin/GetProof
will execute the right handler (if the Key and ID are valid and RegisterRESTHandler
is called on GetProof
).
Another example is curl -i --data '{}' http://127.0.0.1:7771/v3/Status/Request
, the service should output a valid json. Note that you must always send a valid json even if the message is empty, in which case you send {}
.
Answers (my opinions):
- no, because 2.
- no , if we do REST, we do it right. Idempotent calls are GET (this solves the
--data '{}'
thing as well. - I agree: new handler
- REST interfaces use HTTP error codes as much as possible. See Github API for a nice example: https://developer.github.com/v3/#client-errors
https://api.github.com/users/jeffallen/keys -> code 200, body is JSON result https://api.github.com/users/jeffallen/keys/extra -> code 404, body is JSON error:
{
"message": "Not Found",
"documentation_url": "https://developer.github.com/v3"
}
The infrastructure for this is dedis/onet#548, available in onet v3.0.13. Edited the title to indicate this issue tracks making use of the new system for OCS.
@ineiti Is this still an interesting feature for Byzgen? Should we implement it for v4?
ByzGen is currently undergoing restructuring, I have no news for the moment.
I had a short discussion today with @Gilthoniel on how to change the conode<->conode communication. Perhaps there is a possibility to build something in common for those two use-cases?
Perhaps there is a possibility to build something in common for those two use-cases
Yes, but I guess that for the sake of performance a server<->server communication would be better with gRpc rather than REST.
gRPC uses HTTP/2 for transportation - so it is a possibility to put both on the same port. Then again, perhaps it's not a good idea ;)
Just a thought.
And for REST in the client->conode communication, you'd have to make sure that the stream function still works, and that the connection is reused.