centrifugal / centrifugo

Scalable real-time messaging server in a language-agnostic way. Self-hosted alternative to Pubnub, Pusher, Ably. Set up once and forever.
https://centrifugal.dev
Apache License 2.0
8.44k stars 598 forks source link

[Question] about deserializing the client protocol in the Centrifugo server. #843

Closed 070hm closed 4 months ago

070hm commented 4 months ago

Hi centrifugo team

We are internally developing a client SDK for Centrifugo using Kotlin Multiplatform technology. Inshallah, once we complete the development, we will share the library as open-source. However, when we started sending command protocols to the server, we encountered an issue on the server side.

private suspend fun sendConnect() {
        log.d { "sendConnect() connect" }
        val connectRequest = ConnectRequest().newBuilder()
        if (options.token.isNotEmpty()) connectRequest.token(options.token)
        if (options.name.isNotBlankOrEmptyOrNull()) connectRequest.name(options.name)
        if (options.version.isNotEmpty()) connectRequest.version(options.version)
        if (options.data != null) connectRequest.data_(options.data!!.toByteString())
        //TODO see line 816 in the java client

        val cmd: Command = Command.Builder()
            .id(getNextId())
            .connect(connectRequest.build())
            .build()

        log.d { "sendConnect() cmd: $cmd" }

        val deferred = CompletableDeferred<Reply?>()
        deferredTasks[cmd.id] = deferred
        coroutineScope.launch {
            val reply = deferred.await()
            log.d { "connect reply: $reply" }
            deferredTasks.remove(cmd.id)
            if (reply != null) {
                handleConnectReply(reply)
            }
        }
        log.d { "send connect command" }
        val cmdEncode: ByteArray = cmd.encode()
        ws?.send(cmdEncode)
    }

We serialize the command to a byte array in the Centrifugo Java client, while the command is serialized to a ByteString. We are unsure why we are encountering the following error from the backend: error=proto: Command: illegal tag 0 (wire type 1) user=. Is there something missing in the Command message?

Note:

FZambia commented 4 months ago

Hello @070hm

Did you set websocket subprotocol to centrifuge-protobuf, like for example in centrifuge-java:

https://github.com/centrifugal/centrifuge-java/blob/b2dc056b2813807d88ad473d679bffbe98ae98d6/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Client.java#L254

FZambia commented 4 months ago

Another possible reason is that you need to send varint-length encoded Protobuf messages - see in centrifuge-java: https://github.com/centrifugal/centrifuge-java/blob/b2dc056b2813807d88ad473d679bffbe98ae98d6/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Client.java#L523 - i.e. each protobuf command must be prepended by its length. Don't forget that client protocol WebSocket frame may contain several messages: length - message - length - message

070hm commented 4 months ago

Hello @070hm

Did you set websocket subprotocol to centrifuge-protobuf, like for example in centrifuge-java:

https://github.com/centrifugal/centrifuge-java/blob/b2dc056b2813807d88ad473d679bffbe98ae98d6/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Client.java#L254

Thanks for your quick response! I have already set the WebSocket protocol to centrifuge-protobuf.

070hm commented 4 months ago

Another possible reason is that you need to send varint-length encoded Protobuf messages - see in centrifuge-java: https://github.com/centrifugal/centrifuge-java/blob/b2dc056b2813807d88ad473d679bffbe98ae98d6/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Client.java#L523 - i.e. each protobuf command must be prepended by its length. Don't forget that client protocol WebSocket frame may contain several messages: length - message - length - message

I will double-check that and give it another try,

070hm commented 4 months ago

Another possible reason is that you need to send varint-length encoded Protobuf messages - see in centrifuge-java: https://github.com/centrifugal/centrifuge-java/blob/b2dc056b2813807d88ad473d679bffbe98ae98d6/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Client.java#L523 - i.e. each protobuf command must be prepended by its length. Don't forget that client protocol WebSocket frame may contain several messages: length - message - length - message

@FZambia Thanks, bro! I solved the issue by structuring the bytes like this:length - message. Is this solution for sending multiple command messages at the same time? and is same with reply message ?

FZambia commented 4 months ago

Is this solution for sending multiple command messages at the same time? and is same with reply message ?

Yes, it's described a bit here – https://centrifugal.dev/docs/transports/client_protocol#top-level-batching

It's up to you whether your SDK will combine multiple commands into one websocket frame when sending from client to server, but it's necessary to support multiple replies in one WS frame coming from server to client – Centrifugo often automatically combines pushes/replies issued closely to each other.

070hm commented 4 months ago

Is this solution for sending multiple command messages at the same time? and is same with reply message ?

Yes, it's described a bit here – https://centrifugal.dev/docs/transports/client_protocol#top-level-batching

It's up to you whether your SDK will combine multiple commands into one websocket frame when sending from client to server, but it's necessary to support multiple replies in one WS frame coming from server to client – Centrifugo often automatically combines pushes/replies issued closely to each other.

Thanks for the explanation. You have a great project, and you're truly solving real problems. Keep up the excellent work.