cothority icon indicating copy to clipboard operation
cothority copied to clipboard

make OCS endpoints available via REST/JSON

Open ineiti opened this issue 5 years ago • 10 comments

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?

ineiti avatar Apr 26 '19 14:04 ineiti

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.

kc1212 avatar Apr 30 '19 09:04 kc1212

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.

ineiti avatar May 01 '19 06:05 ineiti

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:

  1. Should we use POST for all request?
  2. We're using REST but we're not following any of the REST conventions, is that ok?
  3. 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)
  4. 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.

kc1212 avatar May 08 '19 12:05 kc1212

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 {}.

kc1212 avatar May 08 '19 14:05 kc1212

Answers (my opinions):

  1. no, because 2.
  2. no , if we do REST, we do it right. Idempotent calls are GET (this solves the --data '{}' thing as well.
  3. I agree: new handler
  4. 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"
}

jeffallen avatar May 09 '19 11:05 jeffallen

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.

jeffallen avatar May 21 '19 14:05 jeffallen

@ineiti Is this still an interesting feature for Byzgen? Should we implement it for v4?

nkcr avatar Oct 23 '19 14:10 nkcr

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?

ineiti avatar Oct 23 '19 14:10 ineiti

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.

nkcr avatar Oct 23 '19 14:10 nkcr

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.

ineiti avatar Oct 24 '19 04:10 ineiti