grpc / grpc-web

gRPC for Web Clients
https://grpc.io
Apache License 2.0
8.57k stars 763 forks source link

[Feature request] Server calling client's API. #95

Closed kkimdev closed 7 years ago

kkimdev commented 7 years ago

Many popular Javascript RPC libraries support calling client's API from server, e.g., Socket IO. It would be really great if GRPC-WEB supports that so that it can be a viable alternative to those frameworks.

I know GRPC does not support this, but since it's a pretty common pattern on web so I think it's worth to have a discussion on this.

wenbozhu commented 7 years ago

Could you describe any use case you have in mind that would benefit from such a feature? A link to what socket.io does (or its use cases) would be useful too.

Thanks.

nevi-me commented 7 years ago

What I've seen on Google Cloud (Assistant SDK) is that you can have a bi-directional stream that contains event types, and then react based on the event types.

I suppose what @kkimdev might be trying to do is send events to the client from the server without client interaction (other than perhaps joining some channel).

Socket.io has the ability for users to join a channel, and then all messages that are in that channel be forwarded to that channel. With the understanding that grpc-web is created to be a low-level HTTP2 library for the web, I would presume that features like this should be up to library developers to implement?

wenbozhu commented 7 years ago

@nevi-me Correct. gRPC-Web is a low-level RPC stack, on top of which high-level semantics such as push, pubsub could be implemented.

At the wire-layer, direct server-initiated "session" will never be allowed by a browser UA, due to security restriction among other things.

kkimdev commented 7 years ago

Socket IO supports a direct call from server like this: For example, when a client side js have the following:

this.socket.on('setTestFinished', () => {
            this.isFinished = true;
          });

This will be called when a server calls a method through the connected socket object. And this pattern is also identical for client calling a server method.

My personal example for this usage is a multiplayer game. Let's say two players are connected to a server and server shares events happened on clients with other clients. Currently, the only way to achieve server-side-initiated-call in GRPC is using streaming, and that's what I have been using. However, this is very inconvenient as I'm using GRPC's higher-level feature that's not intended for this use case. And thus lose, say, RPC-ness.

service Game {
    rpc BidirectionalStream(stream ClientMsg) returns (stream ServerMsg) {}
}

message ServerMsg {
    oneof msg {
        SetPlayerId set_player_id = 1;
        NotifyPlayerFrameKeyEvents notify_player_frame_key_events = 2;
        NotifyPlayerFrameMouseEvents notify_player_frame_mouse_events = 3;
    }
}

SetPlayerId, NotifyPlayerFrameKeyEvents, NotifyPlayerFrameMouseEvents are supposed to be a single RPC call each, but since there is no way for server to initiate that, I need to squeeze them in oneof msg {} and embed in streaming RPC call.

To me, this is more like a hack to get around the lack of low-level primitive in GRPC, server-side-initiated-call. I strongly suspect that GRPC didn't need this when it was used at Google since they can simply run another server instance on client side and make it bi-directional if desired, which is not the case for web.

kkimdev commented 7 years ago

A common tutorial example for web RPC frameworks is a chat application, which also rely on server-side-initiated-call. E.g., for Socket IO https://socket.io/get-started/chat/ .

wenbozhu commented 7 years ago

No, we don't use server-initiated RPCs for such a use case, for which pubsub offers a better model (more scalable, reliable).

Google cloud pubsub uses gRPC too as the IDL: https://cloud.google.com/pubsub/

nevi-me commented 7 years ago

@kkimdev I don't think there's a way to move away from using oneof, hence I was saying that Google Assistant does something similar, where the message includes an event type + speech data.

In their case, speech data will be the same message type, but in your case you'll need one of.

I understand the logic that each event type should be a separate RPC call, but you will need a single bidirectional stream.

If you look into the client-side implementation of socket.io, you'll notice that there's a single ws:// connection that is created to act as a stream, and the server example you are showing mainly sends an event with payload attached. The client has to listen for that event, and react accordingly.

As an aside, I have an Android app which I used socket.io-java + Retrofit for. I've removed Retrofit by implementing gRPC, but was left with the socket stuff for chat. I'm currently testing removing socket and relaxing it with bi-di streaming.

The downside is that I have to manage channels myself, and figuring out who to efficiently send what message. For example, when user A sends a message, I should relay that message to all other interested users, but A shouldn't get that message too. It does put the work on our hands, but then again grpc-web is the transport layer, and we'd have to build such features ourselves.

kkimdev commented 7 years ago

@kkimdev I don't think there's a way to move away from using oneof

Yes, so that's why I opened this issue.

Server-initiated-call is not a high-level feature. It's exactly the same as client-initiated-call but just a reversed direction, so I'd say it belongs to transport layer impl. Other pub/sub or high-level patterns can be built on top of it.

nevi-me commented 7 years ago

@kkimdev I'd argue otherwise, because a bi-di stream achieves what you're looking for, and as @wenbozhu has mentioned earlier:

At the wire-layer, direct server-initiated "session" will never be allowed by a browser UA, due to security restriction among other things.

Socket.io does what we're talking about 'easily' because it's the same as having an untyped bi-di stream between the client and the server. gRPC-web gives us that, expect that we have to implement other cosmetics on top, like socket.io did to WebSockets.

kkimdev commented 7 years ago

OK I think there is a confusion here. When I said "server-initiated-call", I didn't mean "server-initiated-session".

And for stream, stream is a higher-level primitive than a call, since conceptually a call is a stream with one send and one receive. Of course stream can emulate a call because it's more generic. I understand that what you're saying is that it is not impossible to do "server-initiated-call" because one can use bi-di stream for that. Well, of course, bi-di can technically do anything, including the existing Unary RPC, Server streaming RPC, and Client streaming RPC, because it's the most generic form.

wenbozhu commented 7 years ago

@kkimdev the particular feature you were asking about, being able to dispatch server-generated messages to client-side "methods" is beyond the IDL (protobuf services) that gRPC supports.

We will keep this feature in mind as we integrate gRPC into existing/future frameworks.

kkimdev commented 7 years ago

Thanks!