centrifugal / centrifuge-java

General Java and Android client SDK for bidirectional communication with Centrifugo and Centrifuge-based server over WebSocket
MIT License
65 stars 34 forks source link

disconnected stale, reconnect false error on android #20

Open puryagh opened 4 years ago

puryagh commented 4 years ago

hi dear

i use your java library on android with kotlin implemention but when i try to connect with JWT token, returns disconnected stale, reconnect false error but on iOS(swift) and Web version every thing is ok

this is my code

`
val cfListener = object : EventListener() { override fun onConnect(client: Client?, event: ConnectEvent?) { Log.d("CENTRIFUGE", "connected") }

        override fun onDisconnect(client: Client?, event: DisconnectEvent) {
            Log.d(
                "CENTRIFUGE",
                "disconnected %s, reconnect %s%n".format(event.reason, event.reconnect)
            )
        }

        override fun onError(client: Client?, event: ErrorEvent?) {
            super.onError(client, event)
            Log.d("CENTRIFUGE", event.toString())
        }
    }

    Log.d("CENTRIFUGE", Globals.CENTRIFUGE_TOKEN)

    val cfClient = Client(
        Constants.centrifugeBrokerUrl,
        Options(),
        cfListener
    )

    cfClient.setToken(Globals.CENTRIFUGE_TOKEN)
    cfClient.connect()

    val cfSubListener = object : SubscriptionEventListener() {
        override fun onSubscribeSuccess(sub: Subscription, event: SubscribeSuccessEvent?) {
            Log.d("CENTRIFUGE", "subscribed to " + sub.channel)
        }

        override fun onSubscribeError(sub: Subscription, event: SubscribeErrorEvent) {
            Log.d(
                "CENTRIFUGE",
                "subscribe error " + sub.channel.toString() + " " + event.message
            )
        }

        override fun onPublish(sub: Subscription, event: PublishEvent) {
            val data = String(event.data, UTF_8)
            Log.d("CENTRIFUGE", "message from " + sub.channel.toString() + " " + data)
        }
    }

    try {
        val subscription = cfClient.newSubscription("chat#${Globals.SHOP_ID}", cfSubListener)
        subscription.subscribe()
    }catch (e: DuplicateSubscriptionException){
        Log.d("CENTRIFUGE", e.message)
    }`
FZambia commented 4 years ago

Hello, could you please provide a bit more information: 1) Version of centrifuge-java 2) Version of server 3) Does stale disconnect reproduces reliably? 4) Do you have any proxy between client and server? Or you just run server locally?

puryagh commented 4 years ago

hi 1 - java client library version is 0.0.5 2 - server version is 2.7.2 3 - after 3 days and testing for near 2000 time, my android app couldn't connect to centrifugo server even 1 time, but ios and web version worked fine 4 - i tested it with both instance, behind Nginx and locally instance and just android not worked

puryagh commented 4 years ago

OOOps!!! i find reason of problem my android project is based on KOTLIN when i create a new android JAVA project, library worked fine but i don't know why when use library in kotlin project, it couldn't work

puryagh commented 4 years ago

i trace library code

in Client.java at line 617 progress of code stoped and next block not run

Protocol.ConnectRequest req = Protocol.ConnectRequest.newBuilder() .setToken(this.token) .build();

FZambia commented 4 years ago

Hmm, that's strange. Thanks for finding this. Since I never used Kotlin personally will appreciate any help in investigating why this happens.

FZambia commented 4 years ago

Just tried to create basic Kotlin project and it works for me.

I started with Basic Activity template, selected Kotlin language. Code inside MainActivity:

package com.example.myfirstapp

import android.annotation.SuppressLint
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.snackbar.Snackbar
import io.github.centrifugal.centrifuge.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)

        val listener: EventListener = object : EventListener() {
            @SuppressLint("SetTextI18n")
            override fun onConnect(client: Client?, event: ConnectEvent) {
                println("connect")
            }

            @SuppressLint("SetTextI18n")
            override fun onDisconnect(client: Client?, event: DisconnectEvent) {
                println("disconnect")
            }
        }

        val client = Client(
                "ws://192.168.1.34:8000/connection/websocket?format=protobuf",
                Options(),
                listener
        )
        client.setToken("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0c3VpdGVfand0In0.hPmHsVqvtY88PvK4EmJlcdwNuKFuy3BGaF7dMaKdPlw")
        client.connect()

        fab.setOnClickListener { view ->
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show()
        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        return when (item.itemId) {
            R.id.action_settings -> true
            else -> super.onOptionsItemSelected(item)
        }
    }
}

Set internet permission inside AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />

After running application I see successful connect in Centrifugo logs:

2020-11-10 02:12:03 [DBG] client connection established client=5967b2de-a6b4-41c9-aa79-ee085cb77b53 transport=websocket
2020-11-10 02:12:04 [DBG] client authenticated client=5967b2de-a6b4-41c9-aa79-ee085cb77b53 user=testsuite_jwt
puryagh commented 4 years ago

yes, it worked in empty android project but in my case

I have GRPC backend service and in prohect gradle already implemented

com.google.protobuf:protobuf-java

now that add centrifuge-java dependency to gradle, protobuf-java must be excluded on it

implementation('io.github.centrifugal:centrifuge-java:0.0.5') { exclude group: 'com.google.protobuf', module: 'protobuf-java' }

if i exclude protobuf-java centrifuge not work 😩 and if don't exclude it duplicated package error occurred

FZambia commented 4 years ago

Maybe GRPC depends on protobuf-javalite package?

Could you try an opposite and exclude javalite package from GRPC instead? I found similar problem in Centrifugo Telegram chat history:

Снимок экрана 2020-11-10 в 17 25 42

Solution as text:

Fixed this by adding next lines:
    implementation ("com.google.firebase:firebase-perf:19.0.5", {
        exclude group: 'com.google.protobuf', module: 'protobuf-javalite'
        exclude group: 'com.google.protobuf', module: 'protobuf-lite'
    })

So the same problem happened with Firebase. I suppose this should work for GRPC too.

By the way there is a pr that migrates centrifuge-java to protobuf-javalitehttps://github.com/centrifugal/centrifuge-java/pull/8

puryagh commented 4 years ago

hi sorry for too long to response thank you for your help

i try this solution before, but it does not worked

finally i use centrifuge-mobile library , its worked perfect

FZambia commented 4 years ago

Looks like I need to test it out myself at some point in the future then. I believe that finding a root cause of this issue is not too hard having a reproducing example (especially since similar stale case already solved by excluding javalite). Personally, I'd go with a native approach instead of centrifuge-mobile especially since you already using centrifuge-swift.

If you can provide a minimal reproducing example this could help a lot

sqrt1764 commented 3 years ago

Greetings I encountered the same issue as puryagh & solved it. It has nothing to do with protobuf. His code was failing because he is missing dependencies. The solution is for him to either include lines 29 & 30 from here https://github.com/centrifugal/centrifuge-java/blob/master/centrifuge/build.gradle#L30 into his project or for you to change implementation to api for those lines, so they are also included for any user of your library.

I also suggest updating this file https://github.com/centrifugal/centrifuge-java/blob/master/README.md When someone new looks at the file it is not clear AT ALL that sections Generate proto & after are in fact documentation for yourself & not for the user of the library. Protobuf-gen of classes is totally irrelevant to the user of the library & is in fact leading him in the wrong direction in solving this issue. There is no need to regenerate classes with protobuf if the protocol has not changed. Hint: separate instructions for yourself into a different file.

sqrt1764 commented 3 years ago

i trace library code

in Client.java at line 617 progress of code stoped and next block not run

Protocol.ConnectRequest req = Protocol.ConnectRequest.newBuilder() .setToken(this.token) .build();

This is happening because he was getting exceptions like this due to the missing libraries I mentioned in my previous comment. Method threw 'java.lang.NoSuchMethodError' exception. Cannot evaluate io.github.centrifugal.centrifuge.internal.protocol.Protocol$ConnectRequest$Builder.toString()

It might be a good idea to introduce some try-catch wrapping & logging in the Runnable's you are passing to your executor, like here - https://github.com/centrifugal/centrifuge-java/blob/master/centrifuge/src/main/java/io/github/centrifugal/centrifuge/Client.java#L185

Library erroring out & swallowing & disappearing all of the crashes is super aggravating 🤬... Yes I had an enjoyable time getting to these solutions /s

FZambia commented 3 years ago

@sqrt1764 hello, thanks, do you have time maybe to send pr with fixes you suggest? Since I am not a Java dev at all I could write absolutely non-idiomatic code here.

FZambia commented 3 years ago

When someone new looks at the file it is not clear AT ALL that sections Generate proto & after are in fact documentation for yourself & not for the user of the library. Protobuf-gen of classes is totally irrelevant to the user of the library & is in fact leading him in the wrong direction in solving this issue. There is no need to regenerate classes with protobuf if the protocol has not changed.

Made this more clear in 6871e7b1be85566962da850dc2962f50ba779096

FZambia commented 3 years ago

I've made a new release 0.0.6 where library migrated to protobuf-javalite and exception in Connect now printed - this may help to detect a dependency issue much faster. Though I still not really understand the root cause here.

FZambia commented 3 years ago

Guys, can someone who experienced this issue try version 0.0.7?

agent10 commented 3 years ago

I have faced with this issue on Android with 0.0.8 lib version. We use JWT token and I encountered error with parsing the token and some fields there were not found. I found that my issue was that Android shrinker removed some generated protobuf classes in Release builds. It seems that the proguard rule should fix it:

-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite {
  <fields>;
}
FZambia commented 3 years ago

@agent10 thanks, could you elaborate more where this rule should be placed and why this happens?

agent10 commented 3 years ago

I'm getting event: onDisconnect, = stale, reconnect = false And in the log I see(it's partly obfuscated): System.err: java.lang.RuntimeException: Field token_ for com.apppackagename.d04 not found. Known fields are [public com.apppackagename.iw3 com.apppackagename.d04.e, public static final com.apppackagename.d04 com.apppackagename.d04.f, public static volatile ccom.apppackagename.rw3 com.apppackagename.d04.g] I'm not sure where it comes from. This rule should be added to the proguard-rules.pro(or any other appropriate names) file inside Android project. But I'm not sure that is possible to add somewhere inside this library. More information about Android shrinker can be found here

Basically it happens because shrinker removes unused classes from the code. But as I know protobuf generated classes are used with reflection and that's why shrinker will remove them as well without explicit rule to keep them in the code. Probably, it also might be the problem that shrinker also obfuscates(and changes the names) fields and these fields just can't be found(with reflection)

FZambia commented 3 years ago

Original issue: https://github.com/protocolbuffers/protobuf/issues/6463

So it looks like Protobuf Javalite uses reflection in releases after 3.11.4 and the official answer there: add proguard rule into a project.

@agent10 have you already had a chance to try a rule you posted above - does it fix a problem?

agent10 commented 3 years ago

Right, it fixes the problem for me.

FZambia commented 3 years ago

Unfortunate thing, just documented this in README - I suppose there is nothing else we can do here.

xingyao1015 commented 2 years ago

In my App ,this problem happened too,can you help me ,thank you . when i use your demo ,the error message is connection error

sqrt1764 commented 2 years ago

This is what your project's module's dependencies should look like when using this library.

implementation "io.github.centrifugal:centrifuge-java:0.0.5"

// `centrifuge-java` dependency is silently crashing when the following is missing
implementation "net.sourceforge.streamsupport:streamsupport-cfuture:1.7.1"
implementation "com.google.protobuf:protobuf-java:3.15.8"

Centrifuge library could solve this in an alternate way by modifying the dependencies of their own. They are also including these same extra dependencies; if they were to include them using api instead of implementation, you would not have to do it manually in your project.

xingyao1015 commented 2 years ago

Thank you for answer my question. I use dependencies same with you,but build error like this :

and then ,i change dependencies like this:

api("io.github.centrifugal:centrifuge-java:0.1.0") { exclude group: "com.google.protobuf", module: "protobuf-java" exclude group: "net.sourceforge.streamsupport", module: "streamsupport-cfuture" }

// centrifuge-java dependency is silently crashing when the following is missing implementation "net.sourceforge.streamsupport:streamsupport-cfuture:1.7.1" implementation "com.google.protobuf:protobuf-java:3.15.8"

but ,still build with same error

last,i only dependencies api "io.github.centrifugal:centrifuge-java:0.1.0" this method can build success,but connect error: {"reason":"bad request","reconnect":false}

xingyao1015 commented 2 years ago

Sorry ,I find the question .It's my error , I have not add "?format=protobuf" to my connect url . Thank you very much.

ntoskrnl commented 2 years ago

Had the same issue with Stale connection on Android.

I was already using protobuf Gradle plugin in my app. There is definitely some problem with how different versions and flavours of protobuf are working with each other. Particularly in my case, there was NoSuchMethodError somewhere in the Protocol class of centrifuge java client (generated with protobuf).

I updated my protobuf to latest version and also switched from protoc-gen-javalite plugin, to a 'lite' built-in. (javalite is now built into protoc). It seems to be working now. Here is some info about the built-in: https://stackoverflow.com/a/60856398

Here is the new configuration that works:

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.20.1"
    }

    generateProtoTasks {
        all().configureEach {
            builtins {
                register("java") {
                    option("lite")
                }
            }
        }
    }
}

dependencies {
    implementation("com.google.protobuf:protobuf-javalite:3.20.1")
}

Here is what I had before the change:

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.12.3"
    }

    plugins {
        id("javalite") {
            artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0"
        }
    }
    generateProtoTasks {
        all().forEach { task ->
            task.plugins {
                id("javalite")
            }
        }
    }
}

dependencies {
    implementation("com.google.protobuf:protobuf-lite:3.0.1")
}

I don't think anything should be changed in centrifuge-java. Just the projects that use protobuf might have some trouble integrating and will have to update their protobuf setup.